blob: 6be708177e89877b14df2882f0db5c3bd686f9d8 [file] [log] [blame]
Petr Cermak9a3380c2018-01-19 15:00:24 +00001/*
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
15package com.android.systemui.statusbar.policy;
16
Petr Cermak102431d2018-01-29 10:36:07 +000017import static android.view.View.MeasureSpec;
18
Petr Cermak9a3380c2018-01-19 15:00:24 +000019import static junit.framework.Assert.assertEquals;
Petr Cermak102431d2018-01-29 10:36:07 +000020import static junit.framework.Assert.assertFalse;
Milo Sredkovb0f55e92018-04-04 16:13:28 +010021import static junit.framework.Assert.assertNull;
Petr Cermak102431d2018-01-29 10:36:07 +000022import static junit.framework.Assert.assertTrue;
23import static junit.framework.Assert.fail;
Petr Cermak9a3380c2018-01-19 15:00:24 +000024
Gustav Senntoneab53682018-11-01 16:30:23 +000025import static org.mockito.ArgumentMatchers.any;
Kenny Guy23991102018-04-05 21:18:38 +010026import static org.mockito.Mockito.mock;
Gustav Sennton8a52dc32019-04-15 12:48:23 +010027import static org.mockito.Mockito.never;
Gustav Senntoneab53682018-11-01 16:30:23 +000028import static org.mockito.Mockito.times;
Kenny Guy23991102018-04-05 21:18:38 +010029import static org.mockito.Mockito.verify;
Kenny Guya0f6de82018-04-06 16:20:16 +010030import static org.mockito.Mockito.when;
Kenny Guy23991102018-04-05 21:18:38 +010031
Kenny Guya0f6de82018-04-06 16:20:16 +010032import android.app.Notification;
Petr Cermak9a3380c2018-01-19 15:00:24 +000033import android.app.PendingIntent;
34import android.app.RemoteInput;
35import android.content.Intent;
36import android.content.IntentFilter;
Petr Cermak102431d2018-01-29 10:36:07 +000037import android.content.res.Resources;
Gustav Senntoneab53682018-11-01 16:30:23 +000038import android.graphics.drawable.Drawable;
39import android.graphics.drawable.Icon;
Kenny Guy23991102018-04-05 21:18:38 +010040import android.service.notification.StatusBarNotification;
Petr Cermak9a3380c2018-01-19 15:00:24 +000041import android.testing.AndroidTestingRunner;
42import android.testing.TestableLooper;
Petr Cermak102431d2018-01-29 10:36:07 +000043import android.view.View;
44import android.view.ViewGroup;
45import android.widget.Button;
46import android.widget.LinearLayout;
Petr Cermak9a3380c2018-01-19 15:00:24 +000047
Brett Chabot84151d92019-02-27 15:37:59 -080048import androidx.test.filters.SmallTest;
49
Gustav Sennton13edb492019-01-28 21:40:04 +000050import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Petr Cermak102431d2018-01-29 10:36:07 +000051import com.android.systemui.R;
Petr Cermak9a3380c2018-01-19 15:00:24 +000052import com.android.systemui.SysuiTestCase;
Gustav Senntoneab53682018-11-01 16:30:23 +000053import com.android.systemui.plugins.ActivityStarter;
54import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
Kenny Guya0f6de82018-04-06 16:20:16 +010055import com.android.systemui.statusbar.SmartReplyController;
Ned Burnsf81c4c42019-01-07 14:10:43 -050056import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Milo Sredkovb0f55e92018-04-04 16:13:28 +010057import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
Gustav Senntoneab53682018-11-01 16:30:23 +000058import com.android.systemui.statusbar.phone.ShadeController;
Petr Cermak9a3380c2018-01-19 15:00:24 +000059
Petr Cermak7ef78122018-02-23 15:54:34 +000060import org.junit.After;
Petr Cermak9a3380c2018-01-19 15:00:24 +000061import org.junit.Before;
62import org.junit.Test;
63import org.junit.runner.RunWith;
Kenny Guy23991102018-04-05 21:18:38 +010064import org.mockito.Mock;
65import org.mockito.MockitoAnnotations;
Petr Cermak9a3380c2018-01-19 15:00:24 +000066
Gustav Senntoneab53682018-11-01 16:30:23 +000067import java.util.ArrayList;
Tony Mak7d4b3a52018-11-27 17:29:36 +000068import java.util.Collections;
Gustav Senntoneab53682018-11-01 16:30:23 +000069import java.util.List;
70import java.util.concurrent.atomic.AtomicReference;
71
Petr Cermak9a3380c2018-01-19 15:00:24 +000072@RunWith(AndroidTestingRunner.class)
73@TestableLooper.RunWithLooper
74@SmallTest
75public class SmartReplyViewTest extends SysuiTestCase {
Petr Cermak9a3380c2018-01-19 15:00:24 +000076 private static final String TEST_RESULT_KEY = "test_result_key";
Selim Cinek9a236f72018-02-22 18:35:53 -080077 private static final String TEST_ACTION = "com.android.SMART_REPLY_VIEW_ACTION";
Petr Cermak102431d2018-01-29 10:36:07 +000078
Petr Cermak9a3380c2018-01-19 15:00:24 +000079 private static final String[] TEST_CHOICES = new String[]{"Hello", "What's up?", "I'm here"};
Kenny Guya0f6de82018-04-06 16:20:16 +010080 private static final String TEST_NOTIFICATION_KEY = "akey";
Petr Cermak9a3380c2018-01-19 15:00:24 +000081
Gustav Senntoneab53682018-11-01 16:30:23 +000082 private static final String[] TEST_ACTION_TITLES = new String[]{
83 "First action", "Open something", "Action"
84 };
85
Petr Cermak102431d2018-01-29 10:36:07 +000086 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
Petr Cermak9a3380c2018-01-19 15:00:24 +000089 private BlockingQueueIntentReceiver mReceiver;
90 private SmartReplyView mView;
Kenny Guya0f6de82018-04-06 16:20:16 +010091 private View mContainer;
Petr Cermak9a3380c2018-01-19 15:00:24 +000092
Gustav Senntoneab53682018-11-01 16:30:23 +000093 private Icon mActionIcon;
94
Petr Cermak102431d2018-01-29 10:36:07 +000095 private int mSingleLinePaddingHorizontal;
96 private int mDoubleLinePaddingHorizontal;
97 private int mSpacing;
98
Kenny Guya0f6de82018-04-06 16:20:16 +010099 @Mock private SmartReplyController mLogger;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500100 private NotificationEntry mEntry;
Kenny Guya0f6de82018-04-06 16:20:16 +0100101 private Notification mNotification;
Gustav Senntona31f6ae2019-01-08 11:20:49 +0000102 @Mock private SmartReplyConstants mConstants;
Kenny Guy23991102018-04-05 21:18:38 +0100103
Gustav Senntoneab53682018-11-01 16:30:23 +0000104 @Mock ActivityStarter mActivityStarter;
Gustav Senntond0e84532018-12-03 16:48:36 +0000105 @Mock HeadsUpManager mHeadsUpManager;
Gustav Senntoneab53682018-11-01 16:30:23 +0000106
Petr Cermak9a3380c2018-01-19 15:00:24 +0000107 @Before
108 public void setUp() {
Kenny Guy23991102018-04-05 21:18:38 +0100109 MockitoAnnotations.initMocks(this);
Petr Cermak9a3380c2018-01-19 15:00:24 +0000110 mReceiver = new BlockingQueueIntentReceiver();
111 mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
Selim Cinekd17b3502019-07-02 20:38:32 -0700112 mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
113 action.onDismiss();
114 });
Gustav Senntoneab53682018-11-01 16:30:23 +0000115 mDependency.injectMockDependency(ShadeController.class);
116 mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
Gustav Senntona31f6ae2019-01-08 11:20:49 +0000117 mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
Petr Cermak9a3380c2018-01-19 15:00:24 +0000118
Kenny Guya0f6de82018-04-06 16:20:16 +0100119 mContainer = new View(mContext, null);
Gustav Sennton5759f872019-02-13 17:25:26 +0000120 mView = SmartReplyView.inflate(mContext);
Petr Cermak102431d2018-01-29 10:36:07 +0000121
Gustav Senntona31f6ae2019-01-08 11:20:49 +0000122 // Any number of replies are fine.
123 when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
124 when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
Gustav Sennton4bf5ff52019-01-16 14:27:25 +0000125 when(mConstants.getMaxNumActions()).thenReturn(-1);
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100126 // Ensure there's no delay before we can click smart suggestions.
127 when(mConstants.getOnClickInitDelay()).thenReturn(0L);
Petr Cermak102431d2018-01-29 10:36:07 +0000128
129 final Resources res = mContext.getResources();
130 mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
131 R.dimen.smart_reply_button_padding_horizontal_single_line);
132 mDoubleLinePaddingHorizontal = res.getDimensionPixelSize(
133 R.dimen.smart_reply_button_padding_horizontal_double_line);
134 mSpacing = res.getDimensionPixelSize(R.dimen.smart_reply_button_spacing);
Kenny Guy23991102018-04-05 21:18:38 +0100135
Kenny Guya0f6de82018-04-06 16:20:16 +0100136 mNotification = new Notification.Builder(mContext, "")
137 .setSmallIcon(R.drawable.ic_person)
138 .setContentTitle("Title")
139 .setContentText("Text").build();
140 StatusBarNotification sbn = mock(StatusBarNotification.class);
141 when(sbn.getNotification()).thenReturn(mNotification);
142 when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500143 mEntry = new NotificationEntry(sbn);
Gustav Senntoneab53682018-11-01 16:30:23 +0000144
145 mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
Petr Cermak9a3380c2018-01-19 15:00:24 +0000146 }
147
Petr Cermak7ef78122018-02-23 15:54:34 +0000148 @After
149 public void tearDown() {
150 mContext.unregisterReceiver(mReceiver);
151 }
152
Petr Cermak9a3380c2018-01-19 15:00:24 +0000153 @Test
154 public void testSendSmartReply_intentContainsResultsAndSource() throws InterruptedException {
Gustav Senntoneab53682018-11-01 16:30:23 +0000155 setSmartReplies(TEST_CHOICES);
Petr Cermak9a3380c2018-01-19 15:00:24 +0000156
157 mView.getChildAt(2).performClick();
158
159 Intent resultIntent = mReceiver.waitForIntent();
160 assertEquals(TEST_CHOICES[2],
161 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
162 assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
163 }
Petr Cermak102431d2018-01-29 10:36:07 +0000164
165 @Test
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100166 public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
Selim Cinekd17b3502019-07-02 20:38:32 -0700167 mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {});
Gustav Senntoneab53682018-11-01 16:30:23 +0000168 setSmartReplies(TEST_CHOICES);
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100169
170 mView.getChildAt(2).performClick();
171
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100172 assertNull(mReceiver.waitForIntentShortDelay());
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100173 }
174
175 @Test
176 public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
177 AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
Selim Cinekd17b3502019-07-02 20:38:32 -0700178 mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
179 actionRef.set(action);
180 });
Gustav Senntoneab53682018-11-01 16:30:23 +0000181 setSmartReplies(TEST_CHOICES);
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100182
183 mView.getChildAt(2).performClick();
184
185 // No intent until the screen is unlocked.
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100186 assertNull(mReceiver.waitForIntentShortDelay());
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100187
188 actionRef.get().onDismiss();
189
190 // Now the intent should arrive.
191 Intent resultIntent = mReceiver.waitForIntent();
192 assertEquals(TEST_CHOICES[2],
193 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
194 assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
195 }
196
197 @Test
Kenny Guya0f6de82018-04-06 16:20:16 +0100198 public void testSendSmartReply_controllerCalled() {
Gustav Senntoneab53682018-11-01 16:30:23 +0000199 setSmartReplies(TEST_CHOICES);
Kenny Guy23991102018-04-05 21:18:38 +0100200 mView.getChildAt(2).performClick();
Tony Mak29996702018-11-26 16:23:34 +0000201 verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
Milo Sredkov13d88112019-02-01 12:23:24 +0000202 MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
Kenny Guya0f6de82018-04-06 16:20:16 +0100203 }
204
205 @Test
206 public void testSendSmartReply_hidesContainer() {
207 mContainer.setVisibility(View.VISIBLE);
Gustav Senntoneab53682018-11-01 16:30:23 +0000208 setSmartReplies(TEST_CHOICES);
Kenny Guya0f6de82018-04-06 16:20:16 +0100209 mView.getChildAt(0).performClick();
210 assertEquals(View.GONE, mContainer.getVisibility());
Kenny Guy23991102018-04-05 21:18:38 +0100211 }
212
213 @Test
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100214 public void testTapSmartReply_beforeInitDelay_blocked() throws InterruptedException {
215 // 100 seconds is easily enough for our click to always be blocked.
216 when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
217 setSmartReplies(TEST_CHOICES);
218
219 mView.getChildAt(2).performClick();
220
221 assertNull(mReceiver.waitForIntentShortDelay());
222 }
223
224 @Test
225 public void testTapSmartReply_afterInitDelay_clickReceived() throws InterruptedException {
226 final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
227 when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
228 setSmartReplies(TEST_CHOICES);
229
230 Thread.sleep(delayMs);
231 mView.getChildAt(2).performClick();
232
233 // Now the intent should arrive.
234 Intent resultIntent = mReceiver.waitForIntent();
235 assertEquals(TEST_CHOICES[2],
236 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
237 assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
238 }
239
240 @Test
241 public void testTapSmartReply_withoutDelayedOnClickListener_bypassesDelay()
242 throws InterruptedException {
243 // 100 seconds is easily enough for our click to always be blocked.
244 when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
245 setSmartReplies(TEST_CHOICES, false /* useDelayedOnClickListener */);
246
247 mView.getChildAt(2).performClick();
248
249 Intent resultIntent = mReceiver.waitForIntent();
250 assertEquals(TEST_CHOICES[2],
251 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
252 assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
253 }
254
255 @Test
Petr Cermak102431d2018-01-29 10:36:07 +0000256 public void testMeasure_empty() {
257 mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
258 assertEquals(500, mView.getMeasuredWidthAndState());
259 assertEquals(0, mView.getMeasuredHeightAndState());
260 }
261
262 @Test
263 public void testLayout_empty() {
264 mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
265 mView.layout(0, 0, 500, 0);
266 }
267
268
269 // Instead of manually calculating the expected measurement/layout results, we build the
270 // expectations as ordinary linear layouts and then check that the relevant parameters in the
271 // corresponding SmartReplyView and LinearView are equal.
272
273 @Test
274 public void testMeasure_shortChoices() {
275 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello", "Bye"};
276
277 // All choices should be displayed as SINGLE-line smart reply buttons.
278 ViewGroup expectedView = buildExpectedView(choices, 1);
279 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
280
Gustav Senntoneab53682018-11-01 16:30:23 +0000281 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000282 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
283
284 assertEqualMeasures(expectedView, mView);
285 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
286 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
287 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
288 }
289
290 @Test
291 public void testLayout_shortChoices() {
292 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello", "Bye"};
293
294 // All choices should be displayed as SINGLE-line smart reply buttons.
295 ViewGroup expectedView = buildExpectedView(choices, 1);
296 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
297 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
298 10 + expectedView.getMeasuredHeight());
299
Gustav Senntoneab53682018-11-01 16:30:23 +0000300 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000301 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
302 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
303
304 assertEqualLayouts(expectedView, mView);
305 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
306 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
307 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
308 }
309
310 @Test
311 public void testMeasure_choiceWithTwoLines() {
312 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\neveryone", "Bye"};
313
314 // All choices should be displayed as DOUBLE-line smart reply buttons.
315 ViewGroup expectedView = buildExpectedView(choices, 2);
316 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
317
Gustav Senntoneab53682018-11-01 16:30:23 +0000318 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000319 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
320
321 assertEqualMeasures(expectedView, mView);
322 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
323 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
324 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
325 }
326
327 @Test
328 public void testLayout_choiceWithTwoLines() {
329 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\neveryone", "Bye"};
330
331 // All choices should be displayed as DOUBLE-line smart reply buttons.
332 ViewGroup expectedView = buildExpectedView(choices, 2);
333 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
334 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
335 10 + expectedView.getMeasuredHeight());
336
Gustav Senntoneab53682018-11-01 16:30:23 +0000337 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000338 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
339 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
340
341 assertEqualLayouts(expectedView, mView);
342 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
343 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
344 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
345 }
346
347 @Test
348 public void testMeasure_choiceWithThreeLines() {
349 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\nevery\nbody", "Bye"};
350
351 // The choice with three lines should NOT be displayed. All other choices should be
352 // displayed as SINGLE-line smart reply buttons.
353 ViewGroup expectedView = buildExpectedView(new CharSequence[]{"Hi", "Bye"}, 1);
354 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
355
Gustav Senntoneab53682018-11-01 16:30:23 +0000356 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000357 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
358
359 assertEqualMeasures(expectedView, mView);
360 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
361 assertReplyButtonHidden(mView.getChildAt(1));
362 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
363 }
364
365 @Test
366 public void testLayout_choiceWithThreeLines() {
367 final CharSequence[] choices = new CharSequence[]{"Hi", "Hello\nevery\nbody", "Bye"};
368
369 // The choice with three lines should NOT be displayed. All other choices should be
370 // displayed as SINGLE-line smart reply buttons.
371 ViewGroup expectedView = buildExpectedView(new CharSequence[]{"Hi", "Bye"}, 1);
372 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
373 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
374 10 + expectedView.getMeasuredHeight());
375
Gustav Senntoneab53682018-11-01 16:30:23 +0000376 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000377 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
378 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
379
380 assertEqualLayouts(expectedView, mView);
381 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
382 // We don't care about mView.getChildAt(1)'s layout because it's hidden (see
383 // testMeasure_choiceWithThreeLines).
384 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
385 }
386
387 @Test
388 public void testMeasure_squeezeLongest() {
389 final CharSequence[] choices = new CharSequence[]{"Short", "Short", "Looooooong replyyyyy"};
390
391 // All choices should be displayed as DOUBLE-line smart reply buttons.
392 ViewGroup expectedView = buildExpectedView(
393 new CharSequence[]{"Short", "Short", "Looooooong \nreplyyyyy"}, 2);
394 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
395
Gustav Senntoneab53682018-11-01 16:30:23 +0000396 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000397 mView.measure(
398 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
399 MeasureSpec.UNSPECIFIED);
400
401 assertEqualMeasures(expectedView, mView);
402 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
403 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
404 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
405 }
406
407 @Test
408 public void testLayout_squeezeLongest() {
409 final CharSequence[] choices = new CharSequence[]{"Short", "Short", "Looooooong replyyyyy"};
410
411 // All choices should be displayed as DOUBLE-line smart reply buttons.
412 ViewGroup expectedView = buildExpectedView(
413 new CharSequence[]{"Short", "Short", "Looooooong \nreplyyyyy"}, 2);
414 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
415 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
416 10 + expectedView.getMeasuredHeight());
417
Gustav Senntoneab53682018-11-01 16:30:23 +0000418 setSmartReplies(choices);
Petr Cermak102431d2018-01-29 10:36:07 +0000419 mView.measure(
420 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
421 MeasureSpec.UNSPECIFIED);
422 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
423
424 assertEqualLayouts(expectedView, mView);
425 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
426 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
427 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
428 }
429
Kenny Guy48ee6d62018-05-09 16:51:26 +0100430 @Test
431 public void testMeasure_dropLongest() {
432 final CharSequence[] choices = new CharSequence[]{"Short", "Short",
433 "LooooooongUnbreakableReplyyyyy"};
434
435 // Short choices should be shown as single line views
436 ViewGroup expectedView = buildExpectedView(
437 new CharSequence[]{"Short", "Short"}, 1);
438 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
439 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
440 10 + expectedView.getMeasuredHeight());
441
Gustav Senntoneab53682018-11-01 16:30:23 +0000442 setSmartReplies(choices);
Kenny Guy48ee6d62018-05-09 16:51:26 +0100443 mView.measure(
444 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
445 MeasureSpec.UNSPECIFIED);
446 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
447
448 assertEqualLayouts(expectedView, mView);
449 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
450 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
451 assertReplyButtonHidden(mView.getChildAt(2));
452 }
453
Gustav Senntoneab53682018-11-01 16:30:23 +0000454 private void setSmartReplies(CharSequence[] choices) {
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100455 setSmartReplies(choices, true /* useDelayedOnClickListener */);
456 }
457
458 private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
Gustav Sennton5759f872019-02-13 17:25:26 +0000459 mView.resetSmartSuggestions(mContainer);
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100460 List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
461 useDelayedOnClickListener);
Gustav Sennton5759f872019-02-13 17:25:26 +0000462 mView.addPreInflatedButtons(replyButtons);
Tony Mak29996702018-11-26 16:23:34 +0000463 }
464
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100465 private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
466 boolean useDelayedOnClickListener) {
Petr Cermak102431d2018-01-29 10:36:07 +0000467 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
468 new Intent(TEST_ACTION), 0);
469 RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
Tony Mak29996702018-11-26 16:23:34 +0000470 SmartReplyView.SmartReplies smartReplies =
471 new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant);
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100472 return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
473 useDelayedOnClickListener);
Gustav Senntoneab53682018-11-01 16:30:23 +0000474 }
475
476 private Notification.Action createAction(String actionTitle) {
477 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
478 new Intent(TEST_ACTION), 0);
479 return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent).build();
480 }
481
482 private List<Notification.Action> createActions(String[] actionTitles) {
483 List<Notification.Action> actions = new ArrayList<>();
484 for (String title : actionTitles) {
485 actions.add(createAction(title));
486 }
487 return actions;
488 }
489
490 private void setSmartActions(String[] actionTitles) {
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100491 setSmartActions(actionTitles, true /* useDelayedOnClickListener */);
492 }
493
494 private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
Gustav Senntoneab53682018-11-01 16:30:23 +0000495 mView.resetSmartSuggestions(mContainer);
Gustav Sennton5759f872019-02-13 17:25:26 +0000496 List<Button> actions = mView.inflateSmartActions(
Gustav Senntondfa968d2019-09-13 12:00:50 +0100497 getContext(),
Tony Mak7d4b3a52018-11-27 17:29:36 +0000498 new SmartReplyView.SmartActions(createActions(actionTitles), false),
499 mLogger,
Gustav Senntond0e84532018-12-03 16:48:36 +0000500 mEntry,
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100501 mHeadsUpManager,
502 useDelayedOnClickListener);
Gustav Sennton5759f872019-02-13 17:25:26 +0000503 mView.addPreInflatedButtons(actions);
Gustav Senntoneab53682018-11-01 16:30:23 +0000504 }
505
506 private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100507 setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
508 true /* useDelayedOnClickListener */);
Gustav Senntona31f6ae2019-01-08 11:20:49 +0000509 }
510
511 private void setSmartRepliesAndActions(
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100512 CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
513 boolean useDelayedOnClickListener) {
Gustav Sennton5759f872019-02-13 17:25:26 +0000514 mView.resetSmartSuggestions(mContainer);
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100515 List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
516 useDelayedOnClickListener);
Gustav Sennton5759f872019-02-13 17:25:26 +0000517 smartSuggestions.addAll(mView.inflateSmartActions(
Gustav Senntondfa968d2019-09-13 12:00:50 +0100518 getContext(),
Gustav Senntona31f6ae2019-01-08 11:20:49 +0000519 new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
Tony Mak7d4b3a52018-11-27 17:29:36 +0000520 mLogger,
Gustav Senntond0e84532018-12-03 16:48:36 +0000521 mEntry,
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100522 mHeadsUpManager,
523 useDelayedOnClickListener));
Gustav Sennton5759f872019-02-13 17:25:26 +0000524 mView.addPreInflatedButtons(smartSuggestions);
Gustav Senntoneab53682018-11-01 16:30:23 +0000525 }
526
527 private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) {
528 return buildExpectedView(choices, lineCount, new ArrayList<>());
Petr Cermak102431d2018-01-29 10:36:07 +0000529 }
530
531 /** Builds a {@link ViewGroup} whose measures and layout mirror a {@link SmartReplyView}. */
Gustav Senntoneab53682018-11-01 16:30:23 +0000532 private ViewGroup buildExpectedView(
533 CharSequence[] choices, int lineCount, List<Notification.Action> actions) {
Petr Cermak102431d2018-01-29 10:36:07 +0000534 LinearLayout layout = new LinearLayout(mContext);
535 layout.setOrientation(LinearLayout.HORIZONTAL);
536
537 // Baseline alignment causes expected heights to be off by one or two pixels on some
538 // devices.
539 layout.setBaselineAligned(false);
540
541 final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
542 final int paddingHorizontal;
543 switch (lineCount) {
544 case 1:
545 paddingHorizontal = mSingleLinePaddingHorizontal;
546 break;
547 case 2:
548 paddingHorizontal = mDoubleLinePaddingHorizontal;
549 break;
550 default:
551 fail("Invalid line count " + lineCount);
552 return null;
553 }
554
Gustav Senntoneab53682018-11-01 16:30:23 +0000555 // Add smart replies
Petr Cermak102431d2018-01-29 10:36:07 +0000556 Button previous = null;
Tony Mak29996702018-11-26 16:23:34 +0000557 SmartReplyView.SmartReplies smartReplies =
558 new SmartReplyView.SmartReplies(choices, null, null, false);
Kenny Guy23991102018-04-05 21:18:38 +0100559 for (int i = 0; i < choices.length; ++i) {
Gustav Sennton5759f872019-02-13 17:25:26 +0000560 Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100561 null /* SmartReplyController */, null /* NotificationEntry */,
562 true /* useDelayedOnClickListener */);
Petr Cermak102431d2018-01-29 10:36:07 +0000563 current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
564 current.getPaddingBottom());
565 if (previous != null) {
566 ViewGroup.MarginLayoutParams lp =
567 (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
568 if (isRtl) {
569 lp.leftMargin = mSpacing;
570 } else {
571 lp.rightMargin = mSpacing;
572 }
573 }
574 layout.addView(current);
575 previous = current;
576 }
577
Gustav Senntoneab53682018-11-01 16:30:23 +0000578 // Add smart actions
579 for (int i = 0; i < actions.size(); ++i) {
580 Button current = inflateActionButton(actions.get(i));
581 current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
582 current.getPaddingBottom());
583 if (previous != null) {
584 ViewGroup.MarginLayoutParams lp =
585 (ViewGroup.MarginLayoutParams) previous.getLayoutParams();
586 if (isRtl) {
587 lp.leftMargin = mSpacing;
588 } else {
589 lp.rightMargin = mSpacing;
590 }
591 }
592 layout.addView(current);
593 previous = current;
594 }
595
Petr Cermak102431d2018-01-29 10:36:07 +0000596 return layout;
597 }
598
599 private static void assertEqualMeasures(View expected, View actual) {
600 assertEquals(expected.getMeasuredWidth(), actual.getMeasuredWidth());
601 assertEquals(expected.getMeasuredHeight(), actual.getMeasuredHeight());
602 }
603
604 private static void assertReplyButtonShownWithEqualMeasures(View expected, View actual) {
605 assertReplyButtonShown(actual);
606 assertEqualMeasures(expected, actual);
Kenny Guy48ee6d62018-05-09 16:51:26 +0100607 assertEqualPadding(expected, actual);
Petr Cermak102431d2018-01-29 10:36:07 +0000608 }
609
610 private static void assertReplyButtonShown(View view) {
611 assertTrue(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
612 }
613
614 private static void assertReplyButtonHidden(View view) {
615 assertFalse(((SmartReplyView.LayoutParams) view.getLayoutParams()).isShown());
616 }
617
618 private static void assertEqualLayouts(View expected, View actual) {
619 assertEquals(expected.getLeft(), actual.getLeft());
620 assertEquals(expected.getTop(), actual.getTop());
621 assertEquals(expected.getRight(), actual.getRight());
622 assertEquals(expected.getBottom(), actual.getBottom());
623 }
Kenny Guy48ee6d62018-05-09 16:51:26 +0100624
625 private static void assertEqualPadding(View expected, View actual) {
626 assertEquals(expected.getPaddingLeft(), actual.getPaddingLeft());
627 assertEquals(expected.getPaddingTop(), actual.getPaddingTop());
628 assertEquals(expected.getPaddingRight(), actual.getPaddingRight());
629 assertEquals(expected.getPaddingBottom(), actual.getPaddingBottom());
630 }
Gustav Senntoneab53682018-11-01 16:30:23 +0000631
632
633 // =============================================================================================
634 // ============================= Smart Action tests ============================================
635 // =============================================================================================
636
637 @Test
638 public void testTapSmartAction_waitsForKeyguard() throws InterruptedException {
639 setSmartActions(TEST_ACTION_TITLES);
640
641 mView.getChildAt(2).performClick();
642
Selim Cinekab4589a2019-04-29 18:42:59 -0700643 verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
644 any());
Gustav Senntoneab53682018-11-01 16:30:23 +0000645 }
646
647 @Test
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100648 public void testTapSmartAction_beforeInitDelay_blocked() throws InterruptedException {
649 // 100 seconds is easily enough for our click to always be blocked.
650 when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
651 setSmartActions(TEST_ACTION_TITLES);
652
653 mView.getChildAt(2).performClick();
654
Selim Cinekab4589a2019-04-29 18:42:59 -0700655 verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(), any());
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100656 }
657
658 @Test
659 public void testTapSmartAction_afterInitDelay_clickReceived() throws InterruptedException {
660 final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
661 when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
662 setSmartActions(TEST_ACTION_TITLES);
663
664 Thread.sleep(delayMs);
665 mView.getChildAt(2).performClick();
666
Selim Cinekab4589a2019-04-29 18:42:59 -0700667 verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
668 any());
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100669 }
670
671 @Test
672 public void testTapSmartAction_withoutDelayedOnClickListener_bypassesDelay() {
673 // 100 seconds is easily enough for our click to always be blocked.
674 when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
675 setSmartActions(TEST_ACTION_TITLES, false /* useDelayedOnClickListener */);
676
677 mView.getChildAt(2).performClick();
678
Selim Cinekab4589a2019-04-29 18:42:59 -0700679 verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
680 any());
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100681 }
682
683 @Test
Gustav Senntoneab53682018-11-01 16:30:23 +0000684 public void testMeasure_shortSmartActions() {
685 String[] actions = new String[] {"Hi", "Hello", "Bye"};
686 // All choices should be displayed as SINGLE-line smart action buttons.
687 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1, createActions(actions));
688 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
689
690 setSmartActions(actions);
691 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
692
693 assertEqualMeasures(expectedView, mView);
694 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
695 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
696 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
697 }
698
699 @Test
700 public void testLayout_shortSmartActions() {
701 String[] actions = new String[] {"Hi", "Hello", "Bye"};
702 // All choices should be displayed as SINGLE-line smart action buttons.
703 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1, createActions(actions));
704 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
705 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
706 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
707 10 + expectedView.getMeasuredHeight());
708
709 setSmartActions(actions);
710 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
711 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
712
713 assertEqualLayouts(expectedView, mView);
714 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
715 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
716 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
717 }
718
719 @Test
720 public void testMeasure_smartActionWithTwoLines() {
721 String[] actions = new String[] {"Hi", "Hello\neveryone", "Bye"};
722
723 // All actions should be displayed as DOUBLE-line smart action buttons.
724 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2, createActions(actions));
725 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
726
727 setSmartActions(actions);
728 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
729
730 assertEqualMeasures(expectedView, mView);
731 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
732 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
733 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
734 }
735
736 @Test
737 public void testLayout_smartActionWithTwoLines() {
738 String[] actions = new String[] {"Hi", "Hello\neveryone", "Bye"};
739
740 // All actions should be displayed as DOUBLE-line smart action buttons.
741 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2, createActions(actions));
742 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
743 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
744 10 + expectedView.getMeasuredHeight());
745
746 setSmartActions(actions);
747 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
748 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
749
750 assertEqualLayouts(expectedView, mView);
751 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
752 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
753 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
754 }
755
756 @Test
757 public void testMeasure_smartActionWithThreeLines() {
758 String[] actions = new String[] {"Hi", "Hello\nevery\nbody", "Bye"};
759
760 // The action with three lines should NOT be displayed. All other actions should be
761 // displayed as SINGLE-line smart action buttons.
762 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1,
763 createActions(new String[]{"Hi", "Bye"}));
764 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
765
766 setSmartActions(actions);
767 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
768
769 assertEqualMeasures(expectedView, mView);
770 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
771 assertReplyButtonHidden(mView.getChildAt(1));
772 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
773 }
774
775 @Test
776 public void testLayout_smartActionWithThreeLines() {
777 String[] actions = new String[] {"Hi", "Hello\nevery\nbody", "Bye"};
778
779 // The action with three lines should NOT be displayed. All other actions should be
780 // displayed as SINGLE-line smart action buttons.
781 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 1,
782 createActions(new String[]{"Hi", "Bye"}));
783 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
784 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
785 10 + expectedView.getMeasuredHeight());
786
787 setSmartActions(actions);
788 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
789 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
790
791 assertEqualLayouts(expectedView, mView);
792 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
793 // We don't care about mView.getChildAt(1)'s layout because it's hidden (see
794 // testMeasure_smartActionWithThreeLines).
795 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(2));
796 }
797
798 @Test
799 public void testMeasure_squeezeLongestSmartAction() {
800 String[] actions = new String[] {"Short", "Short", "Looooooong replyyyyy"};
801
802 // All actions should be displayed as DOUBLE-line smart action buttons.
803 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2,
804 createActions(new String[] {"Short", "Short", "Looooooong \nreplyyyyy"}));
805 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
806
807 setSmartActions(actions);
808 mView.measure(
809 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
810 MeasureSpec.UNSPECIFIED);
811
812 assertEqualMeasures(expectedView, mView);
813 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
814 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
815 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
816 }
817
818 @Test
819 public void testLayout_squeezeLongestSmartAction() {
820 String[] actions = new String[] {"Short", "Short", "Looooooong replyyyyy"};
821
822 // All actions should be displayed as DOUBLE-line smart action buttons.
823 ViewGroup expectedView = buildExpectedView(new CharSequence[0], 2,
824 createActions(new String[] {"Short", "Short", "Looooooong \nreplyyyyy"}));
825 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
826 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
827 10 + expectedView.getMeasuredHeight());
828
829 setSmartActions(actions);
830 mView.measure(
831 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
832 MeasureSpec.UNSPECIFIED);
833 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
834
835 assertEqualLayouts(expectedView, mView);
836 assertEqualLayouts(expectedView.getChildAt(0), mView.getChildAt(0));
837 assertEqualLayouts(expectedView.getChildAt(1), mView.getChildAt(1));
838 assertEqualLayouts(expectedView.getChildAt(2), mView.getChildAt(2));
839 }
840
841 @Test
842 public void testMeasure_dropLongestSmartAction() {
843 String[] actions = new String[] {"Short", "Short", "LooooooongUnbreakableReplyyyyy"};
844
845 // Short actions should be shown as single line views
846 ViewGroup expectedView = buildExpectedView(
847 new CharSequence[0], 1, createActions(new String[] {"Short", "Short"}));
848 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
849 expectedView.layout(10, 10, 10 + expectedView.getMeasuredWidth(),
850 10 + expectedView.getMeasuredHeight());
851
852 setSmartActions(actions);
853 mView.measure(
854 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
855 MeasureSpec.UNSPECIFIED);
856 mView.layout(10, 10, 10 + mView.getMeasuredWidth(), 10 + mView.getMeasuredHeight());
857
858 assertEqualLayouts(expectedView, mView);
859 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
860 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
861 assertReplyButtonHidden(mView.getChildAt(2));
862 }
863
864 private Button inflateActionButton(Notification.Action action) {
Gustav Senntondfa968d2019-09-13 12:00:50 +0100865 return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0,
Tony Mak7d4b3a52018-11-27 17:29:36 +0000866 new SmartReplyView.SmartActions(Collections.singletonList(action), false),
Gustav Sennton8a52dc32019-04-15 12:48:23 +0100867 mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
Gustav Senntoneab53682018-11-01 16:30:23 +0000868 }
869
870 @Test
871 public void testInflateActionButton_smartActionIconSingleLineSizeForTwoLineButton() {
872 // Ensure smart action icons are the same size regardless of the number of text rows in the
873 // button.
874 Button singleLineButton = inflateActionButton(createAction("One line"));
875 Button doubleLineButton = inflateActionButton(createAction("Two\nlines"));
876 Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
877 Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
878 assertEquals(singleLineDrawable.getBounds().width(),
879 doubleLineDrawable.getBounds().width());
880 assertEquals(singleLineDrawable.getBounds().height(),
881 doubleLineDrawable.getBounds().height());
882 }
883
884 @Test
885 public void testMeasure_shortChoicesAndActions() {
886 CharSequence[] choices = new String[] {"Hi", "Hello"};
887 String[] actions = new String[] {"Bye"};
888 // All choices should be displayed as SINGLE-line smart action buttons.
889 ViewGroup expectedView = buildExpectedView(choices, 1, createActions(actions));
890 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
891
892 setSmartRepliesAndActions(choices, actions);
893 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
894
895 assertEqualMeasures(expectedView, mView);
896 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
897 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
898 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
899 }
900
901 @Test
902 public void testMeasure_choicesAndActionsSqueezeLongestAction() {
903 CharSequence[] choices = new String[] {"Short", "Short"};
904 String[] actions = new String[] {"Looooooong replyyyyy"};
905
906 // All actions should be displayed as DOUBLE-line smart action buttons.
907 ViewGroup expectedView = buildExpectedView(choices, 2,
908 createActions(new String[] {"Looooooong \nreplyyyyy"}));
909 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
910
911 setSmartRepliesAndActions(choices, actions);
912 mView.measure(
913 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
914 MeasureSpec.UNSPECIFIED);
915
916 assertEqualMeasures(expectedView, mView);
917 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
918 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
919 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
920 }
Gustav Senntonb149a1a2018-11-20 17:25:50 +0000921
922 @Test
923 public void testMeasure_choicesAndActionsPrioritizeActionsOnlyActions() {
924 String[] choices = new String[] {"Reply"};
925 String[] actions = new String[] {"Looooooong actioooon", "second action", "third action"};
926
927 // All actions should be displayed as DOUBLE-line smart action buttons.
928 ViewGroup expectedView = buildExpectedView(new String[0], 2,
929 createActions(new String[] {
930 "Looooooong \nactioooon", "second \naction", "third \naction"}));
931 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
932
933 setSmartRepliesAndActions(choices, actions);
934 mView.measure(
935 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
936 MeasureSpec.UNSPECIFIED);
937
938 assertEqualMeasures(expectedView, mView);
939 // smart replies
940 assertReplyButtonHidden(mView.getChildAt(0));
941 // smart actions
942 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
943 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
944 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
945 }
946
947 @Test
948 public void testMeasure_choicesAndActionsPrioritizeActions() {
949 String[] choices = new String[] {"Short", "longer reply"};
950 String[] actions = new String[] {"Looooooong actioooon", "second action"};
951
952 // All actions should be displayed as DOUBLE-line smart action buttons.
953 ViewGroup expectedView = buildExpectedView(new String[] {"Short"}, 2,
954 createActions(new String[] {"Looooooong \nactioooon", "second \naction"}));
955 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
956
957 setSmartRepliesAndActions(choices, actions);
958 mView.measure(
959 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
960 MeasureSpec.UNSPECIFIED);
961
962 Button firstAction = ((Button) mView.getChildAt(1));
963
964 assertEqualMeasures(expectedView, mView);
965 // smart replies
966 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
967 assertReplyButtonHidden(mView.getChildAt(1));
968 // smart actions
969 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(2));
970 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
971 }
Gustav Sennton35156752018-12-20 10:35:03 +0000972
973 /**
974 * Test to ensure that we try to add all possible actions - if we find one action that's too
975 * long we just skip that one rather than quitting altogether.
976 */
977 @Test
978 public void testMeasure_skipTooLongActions() {
979 String[] choices = new String[] {};
980 String[] actions = new String[] {
981 "a1", "a2", "this action is soooooooo long it's ridiculous", "a4"};
982
983 // All actions should be displayed as DOUBLE-line smart action buttons.
984 ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
985 createActions(new String[] {"a1", "a2", "a4"}));
986 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
987
988 setSmartRepliesAndActions(choices, actions);
989 mView.measure(
990 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
991 MeasureSpec.UNSPECIFIED);
992
993 assertEqualMeasures(expectedView, mView);
994 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
995 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
996 assertReplyButtonHidden(mView.getChildAt(2));
997 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
998 }
999
1000 /**
1001 * Test to ensure that we try to add all possible replies - if we find one reply that's too
1002 * long we just skip that one rather than quitting altogether.
1003 */
1004 @Test
1005 public void testMeasure_skipTooLongReplies() {
1006 String[] choices = new String[] {
1007 "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
1008 String[] actions = new String[] {};
1009
1010 // All replies should be displayed as single-line smart reply buttons.
1011 ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
1012 1 /* lineCount */, Collections.emptyList());
1013 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1014
1015 setSmartRepliesAndActions(choices, actions);
1016 mView.measure(
1017 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1018 MeasureSpec.UNSPECIFIED);
1019
1020 assertEqualMeasures(expectedView, mView);
1021 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
1022 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
1023 assertReplyButtonHidden(mView.getChildAt(2));
1024 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(3));
1025 }
1026
1027 /**
1028 * Test to ensure that we try to add all possible replies and actions - if we find a reply or
1029 * action that's too long we just skip that one rather than quitting altogether.
1030 */
1031 @Test
1032 public void testMeasure_skipTooLongRepliesAndActions() {
1033 String[] choices = new String[] {
1034 "r1", "r2", "this reply is soooooooo long it's ridiculous", "r4"};
1035 String[] actions = new String[] {
1036 "a1", "ThisActionIsSooooooooLongItsRidiculousIPromise"};
1037
1038 // All replies should be displayed as single-line smart reply buttons.
1039 ViewGroup expectedView = buildExpectedView(new String[] {"r1", "r2", "r4"},
1040 1 /* lineCount */, createActions(new String[] {"a1"}));
1041 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1042
1043 setSmartRepliesAndActions(choices, actions);
1044 mView.measure(
1045 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1046 MeasureSpec.UNSPECIFIED);
1047
1048 assertEqualMeasures(expectedView, mView);
1049 assertReplyButtonShownWithEqualMeasures(
1050 expectedView.getChildAt(0), mView.getChildAt(0)); // r1
1051 assertReplyButtonShownWithEqualMeasures(
1052 expectedView.getChildAt(1), mView.getChildAt(1)); // r2
1053 assertReplyButtonHidden(mView.getChildAt(2)); // long reply
1054 assertReplyButtonShownWithEqualMeasures(
1055 expectedView.getChildAt(2), mView.getChildAt(3)); // r4
1056 assertReplyButtonShownWithEqualMeasures(
1057 expectedView.getChildAt(3), mView.getChildAt(4)); // a1
1058 assertReplyButtonHidden(mView.getChildAt(5)); // long action
1059 }
Gustav Senntona31f6ae2019-01-08 11:20:49 +00001060
1061 @Test
1062 public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
1063 when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
1064
1065 // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1066 String[] choices = new String[] {"reply1", "reply2"};
1067 String[] actions = new String[] {"action1"};
1068
1069 ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
1070 createActions(new String[] {"action1"}));
1071 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1072
Gustav Sennton8a52dc32019-04-15 12:48:23 +01001073 setSmartRepliesAndActions(
1074 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
Gustav Senntona31f6ae2019-01-08 11:20:49 +00001075 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1076
1077 assertEqualMeasures(expectedView, mView);
1078 // smart replies
1079 assertReplyButtonHidden(mView.getChildAt(0));
1080 assertReplyButtonHidden(mView.getChildAt(1));
1081 // smart actions
1082 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
1083 }
1084
1085 @Test
1086 public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
1087 when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
1088
1089 // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1090 String[] choices = new String[] {"reply1", "reply2"};
1091 String[] actions = new String[] {"action1"};
1092
1093 ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
1094 createActions(new String[] {"action1"}));
1095 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1096
Gustav Sennton8a52dc32019-04-15 12:48:23 +01001097 setSmartRepliesAndActions(
1098 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
Gustav Senntona31f6ae2019-01-08 11:20:49 +00001099 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1100
1101 assertEqualMeasures(expectedView, mView);
1102 // smart replies
1103 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
1104 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
1105 // smart actions
1106 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
1107 }
1108
1109 /**
1110 * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
1111 * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
1112 * flag).
1113 */
1114 @Test
1115 public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
1116 when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
1117
1118 // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
1119 String[] choices = new String[] {"This is a very long two-line reply."};
1120 String[] actions = new String[] {"Short action"};
1121
1122 // The action should be displayed on one line only - since it fits!
1123 ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
1124 createActions(new String[] {"Short action"}));
1125 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1126
Gustav Sennton8a52dc32019-04-15 12:48:23 +01001127 setSmartRepliesAndActions(
1128 choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
Gustav Senntona31f6ae2019-01-08 11:20:49 +00001129 mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1130
1131 assertEqualMeasures(expectedView, mView);
1132 // smart replies
1133 assertReplyButtonHidden(mView.getChildAt(0));
1134 // smart actions
1135 assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
1136 }
Gustav Sennton4bf5ff52019-01-16 14:27:25 +00001137
1138 /**
1139 * Test that we don't show more than the maximum number of actions declared in {@link
1140 * SmartReplyConstants}.
1141 */
1142 @Test
1143 public void testMeasure_maxNumActions() {
1144 when(mConstants.getMaxNumActions()).thenReturn(2);
1145
1146 String[] choices = new String[] {};
1147 String[] actions = new String[] {"a1", "a2", "a3", "a4"};
1148
1149 // All replies should be displayed as single-line smart reply buttons.
1150 ViewGroup expectedView = buildExpectedView(new String[] {},
1151 1 /* lineCount */, createActions(new String[] {"a1", "a2"}));
1152 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1153
1154 setSmartRepliesAndActions(choices, actions);
1155 mView.measure(
1156 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1157 MeasureSpec.UNSPECIFIED);
1158
1159 assertEqualMeasures(expectedView, mView);
1160 assertReplyButtonShownWithEqualMeasures(
1161 expectedView.getChildAt(0), mView.getChildAt(0)); // a1
1162 assertReplyButtonShownWithEqualMeasures(
1163 expectedView.getChildAt(1), mView.getChildAt(1)); // a2
1164 assertReplyButtonHidden(mView.getChildAt(2)); // a3
1165 assertReplyButtonHidden(mView.getChildAt(3)); // a4
1166 }
1167
1168 /**
1169 * Test that setting maximum number of actions to -1 means there's no limit to number of actions
1170 * to show.
1171 */
1172 @Test
1173 public void testMeasure_maxNumActions_noLimit() {
1174 when(mConstants.getMaxNumActions()).thenReturn(-1);
1175
1176 String[] choices = new String[] {};
1177 String[] actions = new String[] {"a1", "a2", "a3", "a4"};
1178
1179 // All replies should be displayed as single-line smart reply buttons.
1180 ViewGroup expectedView = buildExpectedView(new String[] {},
1181 1 /* lineCount */, createActions(new String[] {"a1", "a2", "a3", "a4"}));
1182 expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
1183
1184 setSmartRepliesAndActions(choices, actions);
1185 mView.measure(
1186 MeasureSpec.makeMeasureSpec(expectedView.getMeasuredWidth(), MeasureSpec.AT_MOST),
1187 MeasureSpec.UNSPECIFIED);
1188
1189 assertEqualMeasures(expectedView, mView);
1190 assertReplyButtonShownWithEqualMeasures(
1191 expectedView.getChildAt(0), mView.getChildAt(0)); // a1
1192 assertReplyButtonShownWithEqualMeasures(
1193 expectedView.getChildAt(1), mView.getChildAt(1)); // a2
1194 assertReplyButtonShownWithEqualMeasures(
1195 expectedView.getChildAt(2), mView.getChildAt(2)); // a3
1196 assertReplyButtonShownWithEqualMeasures(
1197 expectedView.getChildAt(3), mView.getChildAt(3)); // a4
1198 }
Petr Cermak9a3380c2018-01-19 15:00:24 +00001199}