OSDN Git Service

Merge "docs: Add documentation for equals() method" into qt-dev
[android-x86/frameworks-base.git] / packages / SystemUI / tests / src / com / android / systemui / statusbar / policy / SmartReplyViewTest.java
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14
15 package com.android.systemui.statusbar.policy;
16
17 import static android.view.View.MeasureSpec;
18
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.assertFalse;
21 import static junit.framework.Assert.assertNull;
22 import static junit.framework.Assert.assertTrue;
23 import static junit.framework.Assert.fail;
24
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31
32 import android.app.Notification;
33 import android.app.PendingIntent;
34 import android.app.RemoteInput;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.res.Resources;
38 import android.graphics.drawable.Drawable;
39 import android.graphics.drawable.Icon;
40 import android.service.notification.StatusBarNotification;
41 import android.testing.AndroidTestingRunner;
42 import android.testing.TestableLooper;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.Button;
46 import android.widget.LinearLayout;
47
48 import androidx.test.filters.SmallTest;
49
50 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
51 import com.android.systemui.R;
52 import com.android.systemui.SysuiTestCase;
53 import com.android.systemui.plugins.ActivityStarter;
54 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
55 import com.android.systemui.statusbar.SmartReplyController;
56 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
57 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
58 import com.android.systemui.statusbar.phone.ShadeController;
59
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.Mock;
65 import org.mockito.MockitoAnnotations;
66
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.List;
70 import java.util.concurrent.atomic.AtomicReference;
71
72 @RunWith(AndroidTestingRunner.class)
73 @TestableLooper.RunWithLooper
74 @SmallTest
75 public class SmartReplyViewTest extends SysuiTestCase {
76     private static final String TEST_RESULT_KEY = "test_result_key";
77     private static final String TEST_ACTION = "com.android.SMART_REPLY_VIEW_ACTION";
78
79     private static final String[] TEST_CHOICES = new String[]{"Hello", "What's up?", "I'm here"};
80     private static final String TEST_NOTIFICATION_KEY = "akey";
81
82     private static final String[] TEST_ACTION_TITLES = new String[]{
83             "First action", "Open something", "Action"
84     };
85
86     private static final int WIDTH_SPEC = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
87     private static final int HEIGHT_SPEC = MeasureSpec.makeMeasureSpec(400, MeasureSpec.AT_MOST);
88
89     private BlockingQueueIntentReceiver mReceiver;
90     private SmartReplyView mView;
91     private View mContainer;
92
93     private Icon mActionIcon;
94
95     private int mSingleLinePaddingHorizontal;
96     private int mDoubleLinePaddingHorizontal;
97     private int mSpacing;
98
99     @Mock private SmartReplyController mLogger;
100     private NotificationEntry mEntry;
101     private Notification mNotification;
102     @Mock private SmartReplyConstants mConstants;
103
104     @Mock ActivityStarter mActivityStarter;
105     @Mock HeadsUpManager mHeadsUpManager;
106
107     @Before
108     public void setUp() {
109         MockitoAnnotations.initMocks(this);
110         mReceiver = new BlockingQueueIntentReceiver();
111         mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
112         mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss());
113         mDependency.injectMockDependency(ShadeController.class);
114         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
115         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
116
117         mContainer = new View(mContext, null);
118         mView = SmartReplyView.inflate(mContext);
119
120         // Any number of replies are fine.
121         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
122         when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
123         when(mConstants.getMaxNumActions()).thenReturn(-1);
124         // Ensure there's no delay before we can click smart suggestions.
125         when(mConstants.getOnClickInitDelay()).thenReturn(0L);
126
127         final Resources res = mContext.getResources();
128         mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
129                 R.dimen.smart_reply_button_padding_horizontal_single_line);
130         mDoubleLinePaddingHorizontal = res.getDimensionPixelSize(
131                 R.dimen.smart_reply_button_padding_horizontal_double_line);
132         mSpacing = res.getDimensionPixelSize(R.dimen.smart_reply_button_spacing);
133
134         mNotification = new Notification.Builder(mContext, "")
135                 .setSmallIcon(R.drawable.ic_person)
136                 .setContentTitle("Title")
137                 .setContentText("Text").build();
138         StatusBarNotification sbn = mock(StatusBarNotification.class);
139         when(sbn.getNotification()).thenReturn(mNotification);
140         when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY);
141         mEntry = new NotificationEntry(sbn);
142
143         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
144     }
145
146     @After
147     public void tearDown() {
148         mContext.unregisterReceiver(mReceiver);
149     }
150
151     @Test
152     public void testSendSmartReply_intentContainsResultsAndSource() throws InterruptedException {
153         setSmartReplies(TEST_CHOICES);
154
155         mView.getChildAt(2).performClick();
156
157         Intent resultIntent = mReceiver.waitForIntent();
158         assertEquals(TEST_CHOICES[2],
159                 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
160         assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
161     }
162
163     @Test
164     public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
165         mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> {});
166         setSmartReplies(TEST_CHOICES);
167
168         mView.getChildAt(2).performClick();
169
170         assertNull(mReceiver.waitForIntentShortDelay());
171     }
172
173     @Test
174     public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
175         AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
176         mDependency.get(KeyguardDismissUtil.class).setDismissHandler(actionRef::set);
177         setSmartReplies(TEST_CHOICES);
178
179         mView.getChildAt(2).performClick();
180
181         // No intent until the screen is unlocked.
182         assertNull(mReceiver.waitForIntentShortDelay());
183
184         actionRef.get().onDismiss();
185
186         // Now the intent should arrive.
187         Intent resultIntent = mReceiver.waitForIntent();
188         assertEquals(TEST_CHOICES[2],
189                 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
190         assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
191     }
192
193     @Test
194     public void testSendSmartReply_controllerCalled() {
195         setSmartReplies(TEST_CHOICES);
196         mView.getChildAt(2).performClick();
197         verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
198                 MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
199     }
200
201     @Test
202     public void testSendSmartReply_hidesContainer() {
203         mContainer.setVisibility(View.VISIBLE);
204         setSmartReplies(TEST_CHOICES);
205         mView.getChildAt(0).performClick();
206         assertEquals(View.GONE, mContainer.getVisibility());
207     }
208
209     @Test
210     public void testTapSmartReply_beforeInitDelay_blocked() throws InterruptedException {
211         // 100 seconds is easily enough for our click to always be blocked.
212         when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
213         setSmartReplies(TEST_CHOICES);
214
215         mView.getChildAt(2).performClick();
216
217         assertNull(mReceiver.waitForIntentShortDelay());
218     }
219
220     @Test
221     public void testTapSmartReply_afterInitDelay_clickReceived() throws InterruptedException {
222         final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
223         when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
224         setSmartReplies(TEST_CHOICES);
225
226         Thread.sleep(delayMs);
227         mView.getChildAt(2).performClick();
228
229         // Now the intent should arrive.
230         Intent resultIntent = mReceiver.waitForIntent();
231         assertEquals(TEST_CHOICES[2],
232                 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
233         assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
234     }
235
236     @Test
237     public void testTapSmartReply_withoutDelayedOnClickListener_bypassesDelay()
238             throws InterruptedException {
239         // 100 seconds is easily enough for our click to always be blocked.
240         when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
241         setSmartReplies(TEST_CHOICES, false /* useDelayedOnClickListener */);
242
243         mView.getChildAt(2).performClick();
244
245         Intent resultIntent = mReceiver.waitForIntent();
246         assertEquals(TEST_CHOICES[2],
247                 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
248         assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
249     }
250
251     @Test
252     public void testMeasure_empty() {
253         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
254         assertEquals(500, mView.getMeasuredWidthAndState());
255         assertEquals(0, mView.getMeasuredHeightAndState());
256     }
257
258     @Test
259     public void testLayout_empty() {
260         mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
261         mView.layout(0, 0, 500, 0);
262     }
263
264
265     // Instead of manually calculating the expected measurement/layout results, we build the
266     // expectations as ordinary linear layouts and then check that the relevant parameters in the
267     // corresponding SmartReplyView and LinearView are equal.
268
269     @Test
270     public void testMeasure_shortChoices() {
271         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello", "Bye"};
272
273         // All choices should be displayed as SINGLE-line smart reply buttons.
274         ViewGroup expectedView = buildExpectedView(choices, 1);
275         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
276
277         setSmartReplies(choices);
278         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
279
280         assertEqualMeasures(expectedView, mView);
281         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
282         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
283         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
284     }
285
286     @Test
287     public void testLayout_shortChoices() {
288         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello", "Bye"};
289
290         // All choices should be displayed as SINGLE-line smart reply buttons.
291         ViewGroup expectedView = buildExpectedView(choices, 1);
292         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
293         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
294                 10 + expectedView.getMeasuredHeight());
295
296         setSmartReplies(choices);
297         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
298         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
299
300         assertEqualLayouts(expectedView, mView);
301         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
302         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
303         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
304     }
305
306     @Test
307     public void testMeasure_choiceWithTwoLines() {
308         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\neveryone", "Bye"};
309
310         // All choices should be displayed as DOUBLE-line smart reply buttons.
311         ViewGroup expectedView = buildExpectedView(choices, 2);
312         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
313
314         setSmartReplies(choices);
315         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
316
317         assertEqualMeasures(expectedView, mView);
318         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
319         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
320         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
321     }
322
323     @Test
324     public void testLayout_choiceWithTwoLines() {
325         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\neveryone", "Bye"};
326
327         // All choices should be displayed as DOUBLE-line smart reply buttons.
328         ViewGroup expectedView = buildExpectedView(choices, 2);
329         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
330         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
331                 10 + expectedView.getMeasuredHeight());
332
333         setSmartReplies(choices);
334         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
335         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
336
337         assertEqualLayouts(expectedView, mView);
338         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
339         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
340         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
341     }
342
343     @Test
344     public void testMeasure_choiceWithThreeLines() {
345         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\nevery\nbody", "Bye"};
346
347         // The choice with three lines should NOT be displayed. All other choices should be
348         // displayed as SINGLE-line smart reply buttons.
349         ViewGroup expectedView = buildExpectedView(new CharSequence[]{"Hi", "Bye"}, 1);
350         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
351
352         setSmartReplies(choices);
353         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
354
355         assertEqualMeasures(expectedView, mView);
356         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
357         assertReplyButtonHidden(mView.getChildAt(1));
358         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
359     }
360
361     @Test
362     public void testLayout_choiceWithThreeLines() {
363         final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\nevery\nbody", "Bye"};
364
365         // The choice with three lines should NOT be displayed. All other choices should be
366         // displayed as SINGLE-line smart reply buttons.
367         ViewGroup expectedView = buildExpectedView(new CharSequence[]{"Hi", "Bye"}, 1);
368         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
369         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
370                 10 + expectedView.getMeasuredHeight());
371
372         setSmartReplies(choices);
373         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
374         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
375
376         assertEqualLayouts(expectedView, mView);
377         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
378         // We don't care about mView.getChildAt(1)'s layout because it's hidden (see
379         // testMeasure_choiceWithThreeLines).
380         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
381     }
382
383     @Test
384     public void testMeasure_squeezeLongest() {
385         final CharSequence[] choices = new CharSequence[]{"Short", "Short", "Looooooong replyyyyy"};
386
387         // All choices should be displayed as DOUBLE-line smart reply buttons.
388         ViewGroup expectedView = buildExpectedView(
389                 new CharSequence[]{"Short", "Short", "Looooooong \nreplyyyyy"}, 2);
390         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
391
392         setSmartReplies(choices);
393         mView.measure(
394                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
395                 MeasureSpec.UNSPECIFIED);
396
397         assertEqualMeasures(expectedView, mView);
398         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
399         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
400         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
401     }
402
403     @Test
404     public void testLayout_squeezeLongest() {
405         final CharSequence[] choices = new CharSequence[]{"Short", "Short", "Looooooong replyyyyy"};
406
407         // All choices should be displayed as DOUBLE-line smart reply buttons.
408         ViewGroup expectedView = buildExpectedView(
409                 new CharSequence[]{"Short", "Short", "Looooooong \nreplyyyyy"}, 2);
410         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
411         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
412                 10 + expectedView.getMeasuredHeight());
413
414         setSmartReplies(choices);
415         mView.measure(
416                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
417                 MeasureSpec.UNSPECIFIED);
418         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
419
420         assertEqualLayouts(expectedView, mView);
421         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
422         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
423         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
424     }
425
426     @Test
427     public void testMeasure_dropLongest() {
428         final CharSequence[] choices = new CharSequence[]{"Short", "Short",
429                 "LooooooongUnbreakableReplyyyyy"};
430
431         // Short choices should be shown as single line views
432         ViewGroup expectedView = buildExpectedView(
433                 new CharSequence[]{"Short", "Short"}, 1);
434         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
435         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
436                 10 + expectedView.getMeasuredHeight());
437
438         setSmartReplies(choices);
439         mView.measure(
440                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
441                 MeasureSpec.UNSPECIFIED);
442         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
443
444         assertEqualLayouts(expectedView, mView);
445         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
446         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
447         assertReplyButtonHidden(mView.getChildAt(2));
448     }
449
450     private void setSmartReplies(CharSequence[] choices) {
451         setSmartReplies(choices, true /* useDelayedOnClickListener */);
452     }
453
454     private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
455         mView.resetSmartSuggestions(mContainer);
456         List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
457                 useDelayedOnClickListener);
458         mView.addPreInflatedButtons(replyButtons);
459     }
460
461     private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
462             boolean useDelayedOnClickListener) {
463         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
464                 new Intent(TEST_ACTION), 0);
465         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
466         SmartReplyView.SmartReplies smartReplies =
467                 new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant);
468         return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
469                 useDelayedOnClickListener);
470     }
471
472     private Notification.Action createAction(String actionTitle) {
473         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
474                 new Intent(TEST_ACTION), 0);
475         return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent).build();
476     }
477
478     private List<Notification.Action> createActions(String[] actionTitles) {
479         List<Notification.Action> actions = new ArrayList<>();
480         for (String title : actionTitles) {
481             actions.add(createAction(title));
482         }
483         return actions;
484     }
485
486     private void setSmartActions(String[] actionTitles) {
487         setSmartActions(actionTitles, true /* useDelayedOnClickListener */);
488     }
489
490     private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
491         mView.resetSmartSuggestions(mContainer);
492         List<Button> actions = mView.inflateSmartActions(
493                 new SmartReplyView.SmartActions(createActions(actionTitles), false),
494                 mLogger,
495                 mEntry,
496                 mHeadsUpManager,
497                 useDelayedOnClickListener);
498         mView.addPreInflatedButtons(actions);
499     }
500
501     private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
502         setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
503                 true /* useDelayedOnClickListener */);
504     }
505
506     private void setSmartRepliesAndActions(
507             CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
508             boolean useDelayedOnClickListener) {
509         mView.resetSmartSuggestions(mContainer);
510         List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
511                 useDelayedOnClickListener);
512         smartSuggestions.addAll(mView.inflateSmartActions(
513                 new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
514                 mLogger,
515                 mEntry,
516                 mHeadsUpManager,
517                 useDelayedOnClickListener));
518         mView.addPreInflatedButtons(smartSuggestions);
519     }
520
521     private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) {
522         return buildExpectedView(choices, lineCount, new ArrayList<>());
523     }
524
525     /** Builds a {@link ViewGroup} whose measures and layout mirror a {@link SmartReplyView}. */
526     private ViewGroup buildExpectedView(
527             CharSequence[] choices, int lineCount, List<Notification.Action> actions) {
528         LinearLayout layout = new LinearLayout(mContext);
529         layout.setOrientation(LinearLayout.HORIZONTAL);
530
531         // Baseline alignment causes expected heights to be off by one or two pixels on some
532         // devices.
533         layout.setBaselineAligned(false);
534
535         final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
536         final int paddingHorizontal;
537         switch (lineCount) {
538             case 1:
539                 paddingHorizontal = mSingleLinePaddingHorizontal;
540                 break;
541             case 2:
542                 paddingHorizontal = mDoubleLinePaddingHorizontal;
543                 break;
544             default:
545                 fail("Invalid line count " + lineCount);
546                 return null;
547         }
548
549         // Add smart replies
550         Button previous = null;
551         SmartReplyView.SmartReplies smartReplies =
552                 new SmartReplyView.SmartReplies(choices, null, null, false);
553         for (int i = 0; i < choices.length; ++i) {
554             Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
555                     null /* SmartReplyController */, null /* NotificationEntry */,
556                     true /* useDelayedOnClickListener */);
557             current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
558                     current.getPaddingBottom());
559             if (previous != null) {
560                 ViewGroup.MarginLayoutParams lp =
561                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
562                 if (isRtl) {
563                     lp.leftMargin = mSpacing;
564                 } else {
565                     lp.rightMargin = mSpacing;
566                 }
567             }
568             layout.addView(current);
569             previous = current;
570         }
571
572         // Add smart actions
573         for (int i = 0; i < actions.size(); ++i) {
574             Button current = inflateActionButton(actions.get(i));
575             current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
576                     current.getPaddingBottom());
577             if (previous != null) {
578                 ViewGroup.MarginLayoutParams lp =
579                         (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
580                 if (isRtl) {
581                     lp.leftMargin = mSpacing;
582                 } else {
583                     lp.rightMargin = mSpacing;
584                 }
585             }
586             layout.addView(current);
587             previous = current;
588         }
589
590         return layout;
591     }
592
593     private static void assertEqualMeasures(View expected, View actual) {
594         assertEquals(expected.getMeasuredWidth(), actual.getMeasuredWidth());
595         assertEquals(expected.getMeasuredHeight(), actual.getMeasuredHeight());
596     }
597
598     private static void assertReplyButtonShownWithEqualMeasures(View expected, View actual) {
599         assertReplyButtonShown(actual);
600         assertEqualMeasures(expected, actual);
601         assertEqualPadding(expected, actual);
602     }
603
604     private static void assertReplyButtonShown(View view) {
605         assertTrue(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
606     }
607
608     private static void assertReplyButtonHidden(View view) {
609         assertFalse(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
610     }
611
612     private static void assertEqualLayouts(View expected, View actual) {
613         assertEquals(expected.getLeft(), actual.getLeft());
614         assertEquals(expected.getTop(), actual.getTop());
615         assertEquals(expected.getRight(), actual.getRight());
616         assertEquals(expected.getBottom(), actual.getBottom());
617     }
618
619     private static void assertEqualPadding(View expected, View actual) {
620         assertEquals(expected.getPaddingLeft(), actual.getPaddingLeft());
621         assertEquals(expected.getPaddingTop(), actual.getPaddingTop());
622         assertEquals(expected.getPaddingRight(), actual.getPaddingRight());
623         assertEquals(expected.getPaddingBottom(), actual.getPaddingBottom());
624     }
625
626
627     // =============================================================================================
628     // ============================= Smart Action tests ============================================
629     // =============================================================================================
630
631     @Test
632     public void testTapSmartAction_waitsForKeyguard() throws InterruptedException {
633         setSmartActions(TEST_ACTION_TITLES);
634
635         mView.getChildAt(2).performClick();
636
637         verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
638                 any());
639     }
640
641     @Test
642     public void testTapSmartAction_beforeInitDelay_blocked() throws InterruptedException {
643         // 100 seconds is easily enough for our click to always be blocked.
644         when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
645         setSmartActions(TEST_ACTION_TITLES);
646
647         mView.getChildAt(2).performClick();
648
649         verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(), any());
650     }
651
652     @Test
653     public void testTapSmartAction_afterInitDelay_clickReceived() throws InterruptedException {
654         final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
655         when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
656         setSmartActions(TEST_ACTION_TITLES);
657
658         Thread.sleep(delayMs);
659         mView.getChildAt(2).performClick();
660
661         verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
662                 any());
663     }
664
665     @Test
666     public void testTapSmartAction_withoutDelayedOnClickListener_bypassesDelay() {
667         // 100 seconds is easily enough for our click to always be blocked.
668         when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
669         setSmartActions(TEST_ACTION_TITLES, false /* useDelayedOnClickListener */);
670
671         mView.getChildAt(2).performClick();
672
673         verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
674                 any());
675     }
676
677     @Test
678     public void testMeasure_shortSmartActions() {
679         String[] actions = new String[] {"Hi", "Hello", "Bye"};
680         // All choices should be displayed as SINGLE-line smart action buttons.
681         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1, createActions(actions));
682         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
683
684         setSmartActions(actions);
685         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
686
687         assertEqualMeasures(expectedView, mView);
688         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
689         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
690         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
691     }
692
693     @Test
694     public void testLayout_shortSmartActions() {
695         String[] actions = new String[] {"Hi", "Hello", "Bye"};
696         // All choices should be displayed as SINGLE-line smart action buttons.
697         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1, createActions(actions));
698         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
699         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
700         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
701                 10 + expectedView.getMeasuredHeight());
702
703         setSmartActions(actions);
704         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
705         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
706
707         assertEqualLayouts(expectedView, mView);
708         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
709         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
710         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
711     }
712
713     @Test
714     public void testMeasure_smartActionWithTwoLines() {
715         String[] actions = new String[] {"Hi", "Hello\neveryone", "Bye"};
716
717         // All actions should be displayed as DOUBLE-line smart action buttons.
718         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2, createActions(actions));
719         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
720
721         setSmartActions(actions);
722         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
723
724         assertEqualMeasures(expectedView, mView);
725         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
726         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
727         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
728     }
729
730     @Test
731     public void testLayout_smartActionWithTwoLines() {
732         String[] actions = new String[] {"Hi", "Hello\neveryone", "Bye"};
733
734         // All actions should be displayed as DOUBLE-line smart action buttons.
735         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2, createActions(actions));
736         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
737         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
738                 10 + expectedView.getMeasuredHeight());
739
740         setSmartActions(actions);
741         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
742         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
743
744         assertEqualLayouts(expectedView, mView);
745         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
746         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
747         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
748     }
749
750     @Test
751     public void testMeasure_smartActionWithThreeLines() {
752         String[] actions = new String[] {"Hi", "Hello\nevery\nbody", "Bye"};
753
754         // The action with three lines should NOT be displayed. All other actions should be
755         // displayed as SINGLE-line smart action buttons.
756         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1,
757                 createActions(new String[]{"Hi", "Bye"}));
758         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
759
760         setSmartActions(actions);
761         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
762
763         assertEqualMeasures(expectedView, mView);
764         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
765         assertReplyButtonHidden(mView.getChildAt(1));
766         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
767     }
768
769     @Test
770     public void testLayout_smartActionWithThreeLines() {
771         String[] actions = new String[] {"Hi", "Hello\nevery\nbody", "Bye"};
772
773         // The action with three lines should NOT be displayed. All other actions should be
774         // displayed as SINGLE-line smart action buttons.
775         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1,
776                 createActions(new String[]{"Hi", "Bye"}));
777         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
778         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
779                 10 + expectedView.getMeasuredHeight());
780
781         setSmartActions(actions);
782         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
783         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
784
785         assertEqualLayouts(expectedView, mView);
786         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
787         // We don't care about mView.getChildAt(1)'s layout because it's hidden (see
788         // testMeasure_smartActionWithThreeLines).
789         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
790     }
791
792     @Test
793     public void testMeasure_squeezeLongestSmartAction() {
794         String[] actions = new String[] {"Short", "Short", "Looooooong replyyyyy"};
795
796         // All actions should be displayed as DOUBLE-line smart action buttons.
797         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2,
798                 createActions(new String[] {"Short", "Short", "Looooooong \nreplyyyyy"}));
799         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
800
801         setSmartActions(actions);
802         mView.measure(
803                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
804                 MeasureSpec.UNSPECIFIED);
805
806         assertEqualMeasures(expectedView, mView);
807         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
808         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
809         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
810     }
811
812     @Test
813     public void testLayout_squeezeLongestSmartAction() {
814         String[] actions = new String[] {"Short", "Short", "Looooooong replyyyyy"};
815
816         // All actions should be displayed as DOUBLE-line smart action buttons.
817         ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2,
818                 createActions(new String[] {"Short", "Short", "Looooooong \nreplyyyyy"}));
819         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
820         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
821                 10 + expectedView.getMeasuredHeight());
822
823         setSmartActions(actions);
824         mView.measure(
825                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
826                 MeasureSpec.UNSPECIFIED);
827         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
828
829         assertEqualLayouts(expectedView, mView);
830         assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
831         assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
832         assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
833     }
834
835     @Test
836     public void testMeasure_dropLongestSmartAction() {
837         String[] actions = new String[] {"Short", "Short", "LooooooongUnbreakableReplyyyyy"};
838
839         // Short actions should be shown as single line views
840         ViewGroup expectedView = buildExpectedView(
841                 new CharSequence[0], 1, createActions(new String[] {"Short", "Short"}));
842         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
843         expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
844                 10 + expectedView.getMeasuredHeight());
845
846         setSmartActions(actions);
847         mView.measure(
848                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
849                 MeasureSpec.UNSPECIFIED);
850         mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
851
852         assertEqualLayouts(expectedView, mView);
853         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
854         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
855         assertReplyButtonHidden(mView.getChildAt(2));
856     }
857
858     private Button inflateActionButton(Notification.Action action) {
859         return SmartReplyView.inflateActionButton(mView, getContext(), 0,
860                 new SmartReplyView.SmartActions(Collections.singletonList(action), false),
861                 mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
862     }
863
864     @Test
865     public void testInflateActionButton_smartActionIconSingleLineSizeForTwoLineButton() {
866         // Ensure smart action icons are the same size regardless of the number of text rows in the
867         // button.
868         Button singleLineButton = inflateActionButton(createAction("One line"));
869         Button doubleLineButton = inflateActionButton(createAction("Two\nlines"));
870         Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
871         Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
872         assertEquals(singleLineDrawable.getBounds().width(),
873                      doubleLineDrawable.getBounds().width());
874         assertEquals(singleLineDrawable.getBounds().height(),
875                      doubleLineDrawable.getBounds().height());
876     }
877
878     @Test
879     public void testMeasure_shortChoicesAndActions() {
880         CharSequence[] choices = new String[] {"Hi", "Hello"};
881         String[] actions = new String[] {"Bye"};
882         // All choices should be displayed as SINGLE-line smart action buttons.
883         ViewGroup expectedView = buildExpectedView(choices, 1, createActions(actions));
884         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
885
886         setSmartRepliesAndActions(choices, actions);
887         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
888
889         assertEqualMeasures(expectedView, mView);
890         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
891         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
892         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
893     }
894
895     @Test
896     public void testMeasure_choicesAndActionsSqueezeLongestAction() {
897         CharSequence[] choices = new String[] {"Short", "Short"};
898         String[] actions = new String[] {"Looooooong replyyyyy"};
899
900         // All actions should be displayed as DOUBLE-line smart action buttons.
901         ViewGroup expectedView = buildExpectedView(choices, 2,
902                 createActions(new String[] {"Looooooong \nreplyyyyy"}));
903         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
904
905         setSmartRepliesAndActions(choices, actions);
906         mView.measure(
907                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
908                 MeasureSpec.UNSPECIFIED);
909
910         assertEqualMeasures(expectedView, mView);
911         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
912         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
913         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
914     }
915
916     @Test
917     public void testMeasure_choicesAndActionsPrioritizeActionsOnlyActions() {
918         String[] choices = new String[] {"Reply"};
919         String[] actions = new String[] {"Looooooong actioooon", "second action", "third action"};
920
921         // All actions should be displayed as DOUBLE-line smart action buttons.
922         ViewGroup expectedView = buildExpectedView(new String[0], 2,
923                 createActions(new String[] {
924                         "Looooooong \nactioooon", "second \naction", "third \naction"}));
925         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
926
927         setSmartRepliesAndActions(choices, actions);
928         mView.measure(
929                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
930                 MeasureSpec.UNSPECIFIED);
931
932         assertEqualMeasures(expectedView, mView);
933         // smart replies
934         assertReplyButtonHidden(mView.getChildAt(0));
935         // smart actions
936         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
937         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
938         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
939     }
940
941     @Test
942     public void testMeasure_choicesAndActionsPrioritizeActions() {
943         String[] choices = new String[] {"Short", "longer reply"};
944         String[] actions = new String[] {"Looooooong actioooon", "second action"};
945
946         // All actions should be displayed as DOUBLE-line smart action buttons.
947         ViewGroup expectedView = buildExpectedView(new String[] {"Short"}, 2,
948                 createActions(new String[] {"Looooooong \nactioooon", "second \naction"}));
949         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
950
951         setSmartRepliesAndActions(choices, actions);
952         mView.measure(
953                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
954                 MeasureSpec.UNSPECIFIED);
955
956         Button firstAction = ((Button) mView.getChildAt(1));
957
958         assertEqualMeasures(expectedView, mView);
959         // smart replies
960         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
961         assertReplyButtonHidden(mView.getChildAt(1));
962         // smart actions
963         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
964         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
965     }
966
967     /**
968      * Test to ensure that we try to add all possible actions - if we find one action that's too
969      * long we just skip that one rather than quitting altogether.
970      */
971     @Test
972     public void testMeasure_skipTooLongActions() {
973         String[] choices = new String[] {};
974         String[] actions = new String[] {
975                 "a1", "a2", "this action is soooooooo long it's ridiculous", "a4"};
976
977         // All actions should be displayed as DOUBLE-line smart action buttons.
978         ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
979                 createActions(new String[] {"a1", "a2", "a4"}));
980         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
981
982         setSmartRepliesAndActions(choices, actions);
983         mView.measure(
984                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
985                 MeasureSpec.UNSPECIFIED);
986
987         assertEqualMeasures(expectedView, mView);
988         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
989         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
990         assertReplyButtonHidden(mView.getChildAt(2));
991         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
992     }
993
994     /**
995      * Test to ensure that we try to add all possible replies - if we find one reply that's too
996      * long we just skip that one rather than quitting altogether.
997      */
998     @Test
999     public void testMeasure_skipTooLongReplies() {
1000         String[] choices = new String[] {
1001                 "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
1002         String[] actions = new String[] {};
1003
1004         // All replies should be displayed as single-line smart reply buttons.
1005         ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
1006                 1 /* lineCount */, Collections.emptyList());
1007         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1008
1009         setSmartRepliesAndActions(choices, actions);
1010         mView.measure(
1011                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1012                 MeasureSpec.UNSPECIFIED);
1013
1014         assertEqualMeasures(expectedView, mView);
1015         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
1016         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
1017         assertReplyButtonHidden(mView.getChildAt(2));
1018         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
1019     }
1020
1021     /**
1022      * Test to ensure that we try to add all possible replies and actions - if we find a reply or
1023      * action that's too long we just skip that one rather than quitting altogether.
1024      */
1025     @Test
1026     public void testMeasure_skipTooLongRepliesAndActions() {
1027         String[] choices = new String[] {
1028                 "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
1029         String[] actions = new String[] {
1030                 "a1", "ThisActionIsSooooooooLongItsRidiculousIPromise"};
1031
1032         // All replies should be displayed as single-line smart reply buttons.
1033         ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
1034                 1 /* lineCount */, createActions(new String[] {"a1"}));
1035         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1036
1037         setSmartRepliesAndActions(choices, actions);
1038         mView.measure(
1039                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1040                 MeasureSpec.UNSPECIFIED);
1041
1042         assertEqualMeasures(expectedView, mView);
1043         assertReplyButtonShownWithEqualMeasures(
1044                 expectedView.getChildAt(0), mView.getChildAt(0)); // r1
1045         assertReplyButtonShownWithEqualMeasures(
1046                 expectedView.getChildAt(1), mView.getChildAt(1)); // r2
1047         assertReplyButtonHidden(mView.getChildAt(2)); // long reply
1048         assertReplyButtonShownWithEqualMeasures(
1049                 expectedView.getChildAt(2), mView.getChildAt(3)); // r4
1050         assertReplyButtonShownWithEqualMeasures(
1051                 expectedView.getChildAt(3), mView.getChildAt(4)); // a1
1052         assertReplyButtonHidden(mView.getChildAt(5)); // long action
1053     }
1054
1055     @Test
1056     public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
1057         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
1058
1059         // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1060         String[] choices = new String[] {"reply1", "reply2"};
1061         String[] actions = new String[] {"action1"};
1062
1063         ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
1064                 createActions(new String[] {"action1"}));
1065         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1066
1067         setSmartRepliesAndActions(
1068                 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
1069         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1070
1071         assertEqualMeasures(expectedView, mView);
1072         // smart replies
1073         assertReplyButtonHidden(mView.getChildAt(0));
1074         assertReplyButtonHidden(mView.getChildAt(1));
1075         // smart actions
1076         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
1077     }
1078
1079     @Test
1080     public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
1081         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
1082
1083         // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1084         String[] choices = new String[] {"reply1", "reply2"};
1085         String[] actions = new String[] {"action1"};
1086
1087         ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
1088                 createActions(new String[] {"action1"}));
1089         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1090
1091         setSmartRepliesAndActions(
1092                 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
1093         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1094
1095         assertEqualMeasures(expectedView, mView);
1096         // smart replies
1097         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
1098         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
1099         // smart actions
1100         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
1101     }
1102
1103     /**
1104      * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
1105      * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
1106      * flag).
1107      */
1108     @Test
1109     public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
1110         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
1111
1112         // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1113         String[] choices = new String[] {"This is a very long two-line reply."};
1114         String[] actions = new String[] {"Short action"};
1115
1116         // The action should be displayed on one line only - since it fits!
1117         ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
1118                 createActions(new String[] {"Short action"}));
1119         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1120
1121         setSmartRepliesAndActions(
1122                 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
1123         mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1124
1125         assertEqualMeasures(expectedView, mView);
1126         // smart replies
1127         assertReplyButtonHidden(mView.getChildAt(0));
1128         // smart actions
1129         assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
1130     }
1131
1132     /**
1133      * Test that we don't show more than the maximum number of actions declared in {@link
1134      * SmartReplyConstants}.
1135      */
1136     @Test
1137     public void testMeasure_maxNumActions() {
1138         when(mConstants.getMaxNumActions()).thenReturn(2);
1139
1140         String[] choices = new String[] {};
1141         String[] actions = new String[] {"a1", "a2", "a3", "a4"};
1142
1143         // All replies should be displayed as single-line smart reply buttons.
1144         ViewGroup expectedView = buildExpectedView(new String[] {},
1145                 1 /* lineCount */, createActions(new String[] {"a1", "a2"}));
1146         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1147
1148         setSmartRepliesAndActions(choices, actions);
1149         mView.measure(
1150                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1151                 MeasureSpec.UNSPECIFIED);
1152
1153         assertEqualMeasures(expectedView, mView);
1154         assertReplyButtonShownWithEqualMeasures(
1155                 expectedView.getChildAt(0), mView.getChildAt(0)); // a1
1156         assertReplyButtonShownWithEqualMeasures(
1157                 expectedView.getChildAt(1), mView.getChildAt(1)); // a2
1158         assertReplyButtonHidden(mView.getChildAt(2)); // a3
1159         assertReplyButtonHidden(mView.getChildAt(3)); // a4
1160     }
1161
1162     /**
1163      * Test that setting maximum number of actions to -1 means there's no limit to number of actions
1164      * to show.
1165      */
1166     @Test
1167     public void testMeasure_maxNumActions_noLimit() {
1168         when(mConstants.getMaxNumActions()).thenReturn(-1);
1169
1170         String[] choices = new String[] {};
1171         String[] actions = new String[] {"a1", "a2", "a3", "a4"};
1172
1173         // All replies should be displayed as single-line smart reply buttons.
1174         ViewGroup expectedView = buildExpectedView(new String[] {},
1175                 1 /* lineCount */, createActions(new String[] {"a1", "a2", "a3", "a4"}));
1176         expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1177
1178         setSmartRepliesAndActions(choices, actions);
1179         mView.measure(
1180                 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1181                 MeasureSpec.UNSPECIFIED);
1182
1183         assertEqualMeasures(expectedView, mView);
1184         assertReplyButtonShownWithEqualMeasures(
1185                 expectedView.getChildAt(0), mView.getChildAt(0)); // a1
1186         assertReplyButtonShownWithEqualMeasures(
1187                 expectedView.getChildAt(1), mView.getChildAt(1)); // a2
1188         assertReplyButtonShownWithEqualMeasures(
1189                 expectedView.getChildAt(2), mView.getChildAt(2)); // a3
1190         assertReplyButtonShownWithEqualMeasures(
1191                 expectedView.getChildAt(3), mView.getChildAt(3)); // a4
1192     }
1193 }