1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.  */
15 
16 package com.android.systemui.statusbar.policy;
17 
18 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.argThat;
25 import static org.mockito.Mockito.when;
26 
27 import android.app.Notification;
28 import android.app.PendingIntent;
29 import android.app.RemoteInput;
30 import android.content.Intent;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.ResolveInfo;
33 import android.graphics.drawable.Icon;
34 import android.util.Pair;
35 
36 import androidx.test.annotation.UiThreadTest;
37 import androidx.test.filters.SmallTest;
38 import androidx.test.runner.AndroidJUnit4;
39 
40 import com.android.systemui.SysuiTestCase;
41 import com.android.systemui.res.R;
42 import com.android.systemui.shared.system.ActivityManagerWrapper;
43 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
44 import com.android.systemui.shared.system.PackageManagerWrapper;
45 import com.android.systemui.statusbar.NotificationEntryHelper;
46 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
47 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
48 import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions;
49 import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
50 import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.Mock;
56 import org.mockito.MockitoAnnotations;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.List;
61 
62 @SmallTest
63 @RunWith(AndroidJUnit4.class)
64 public class InflatedSmartRepliesTest extends SysuiTestCase {
65 
66     private static final Intent TEST_INTENT = new Intent("com.android.SMART_REPLY_VIEW_ACTION");
67     private static final Intent ALLOWLISTED_TEST_INTENT =
68             new Intent("com.android.WHITELISTED_TEST_ACTION");
69 
70     @Mock private SmartReplyConstants mSmartReplyConstants;
71     @Mock private Notification mNotification;
72     @Mock private RemoteInput mRemoteInput;
73     @Mock private RemoteInput mFreeFormRemoteInput;
74     @Mock private ActivityManagerWrapper mActivityManagerWrapper;
75     @Mock private PackageManagerWrapper mPackageManagerWrapper;
76     @Mock private DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
77     @Mock private SmartReplyInflater mSmartReplyInflater;
78     @Mock private SmartActionInflater mSmartActionInflater;
79 
80     private Icon mActionIcon;
81     private NotificationEntry mEntry;
82     private SmartReplyStateInflaterImpl mSmartReplyStateInflater;
83 
84     @Before
85     @UiThreadTest
setUp()86     public void setUp() {
87         MockitoAnnotations.initMocks(this);
88 
89         mDependency.injectTestDependency(ActivityManagerWrapper.class, mActivityManagerWrapper);
90         mDependency.injectTestDependency(
91                 DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper);
92         mDependency.injectTestDependency(PackageManagerWrapper.class, mPackageManagerWrapper);
93 
94         when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true);
95         mEntry = new NotificationEntryBuilder()
96                 .setNotification(mNotification)
97                 .build();
98         when(mSmartReplyConstants.isEnabled()).thenReturn(true);
99         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
100 
101         when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
102 
103         mSmartReplyStateInflater = new SmartReplyStateInflaterImpl(
104                 mSmartReplyConstants,
105                 mActivityManagerWrapper,
106                 mPackageManagerWrapper,
107                 mDevicePolicyManagerWrapper,
108                 mSmartReplyInflater,
109                 mSmartActionInflater);
110     }
111 
112     @Test
chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions()113     public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() {
114         CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
115         List<Notification.Action> smartActions =
116                 createActions("Test Action 1", "Test Action 2");
117         setupAppGeneratedSuggestions(smartReplies, smartActions);
118         when(mSmartReplyConstants.isEnabled()).thenReturn(false);
119 
120         InflatedSmartReplyState smartReplyState =
121                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
122 
123         assertThat(smartReplyState.getSmartReplies()).isNull();
124         assertThat(smartReplyState.getSmartActions()).isNull();
125         assertThat(smartReplyState.getSuppressedActions()).isNull();
126         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
127     }
128 
129     @Test
chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions()130     public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() {
131         modifyRanking(mEntry)
132                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
133                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
134                 .build();
135 
136         when(mSmartReplyConstants.isEnabled()).thenReturn(false);
137 
138         InflatedSmartReplyState smartReplyState =
139                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
140 
141         assertThat(smartReplyState.getSmartReplies()).isNull();
142         assertThat(smartReplyState.getSmartActions()).isNull();
143         assertThat(smartReplyState.getSuppressedActions()).isNull();
144         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
145     }
146 
147     @Test
chooseSmartRepliesAndActions_appGeneratedSmartReplies()148     public void chooseSmartRepliesAndActions_appGeneratedSmartReplies() {
149         CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
150         setupAppGeneratedReplies(smartReplies);
151 
152         InflatedSmartReplyState smartReplyState =
153                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
154 
155         assertThat(smartReplyState.getSmartReplies().choices)
156                 .containsExactlyElementsIn(smartReplies).inOrder();
157         assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
158         assertThat(smartReplyState.getSmartActions()).isNull();
159         assertThat(smartReplyState.getSuppressedActions()).isNull();
160         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
161     }
162 
163     @Test
chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions()164     public void chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions() {
165         CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
166         List<Notification.Action> smartActions =
167                 createActions("Test Action 1", "Test Action 2");
168         setupAppGeneratedSuggestions(smartReplies, smartActions);
169 
170         InflatedSmartReplyState smartReplyState =
171                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
172 
173         assertThat(smartReplyState.getSmartReplies().choices)
174                 .containsExactlyElementsIn(smartReplies).inOrder();
175         assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
176         assertThat(smartReplyState.getSmartActions().actions)
177                 .containsExactlyElementsIn(smartActions).inOrder();
178         assertThat(smartReplyState.getSmartActions().fromAssistant).isFalse();
179         assertThat(smartReplyState.getSuppressedActions()).isNull();
180         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
181     }
182 
183     @Test
chooseSmartRepliesAndActions_sysGeneratedSmartReplies()184     public void chooseSmartRepliesAndActions_sysGeneratedSmartReplies() {
185         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
186         // replies.
187         setupAppGeneratedReplies(null /* smartReplies */);
188 
189         modifyRanking(mEntry)
190                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
191                 .build();
192 
193         InflatedSmartReplyState smartReplyState =
194                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
195 
196         assertThat(smartReplyState.getSmartReplies().choices)
197                 .containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
198         assertThat(smartReplyState.getSmartReplies().fromAssistant).isTrue();
199         assertThat(smartReplyState.getSmartActions()).isNull();
200         assertThat(smartReplyState.getSuppressedActions()).isNull();
201         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
202     }
203 
204     @Test
chooseSmartRepliesAndActions_noSysGeneratedSmartRepliesIfNotAllowed()205     public void chooseSmartRepliesAndActions_noSysGeneratedSmartRepliesIfNotAllowed() {
206         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
207         // replies.
208         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
209 
210         NotificationEntryHelper.modifyRanking(mEntry)
211                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
212                 .build();
213         InflatedSmartReplyState smartReplyState =
214                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
215 
216         assertThat(smartReplyState.getSmartReplies()).isNull();
217         assertThat(smartReplyState.getSmartActions()).isNull();
218         assertThat(smartReplyState.getSuppressedActions()).isNull();
219         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
220     }
221 
222     @Test
chooseSmartRepliesAndActions_sysGeneratedSmartActions()223     public void chooseSmartRepliesAndActions_sysGeneratedSmartActions() {
224         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
225         // actions.
226         setupAppGeneratedReplies(null /* smartReplies */);
227 
228         modifyRanking(mEntry)
229                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
230                 .build();
231 
232         InflatedSmartReplyState smartReplyState =
233                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
234 
235         assertThat(smartReplyState.getSmartReplies()).isNull();
236         assertThat(smartReplyState.getSmartActions().actions)
237                 .containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
238         assertThat(smartReplyState.getSmartActions().fromAssistant).isTrue();
239         assertThat(smartReplyState.getSuppressedActions()).isNull();
240         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
241     }
242 
243     @Test
chooseSmartRepliesAndActions_sysGeneratedPhishingSmartAction()244     public void chooseSmartRepliesAndActions_sysGeneratedPhishingSmartAction() {
245         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
246         // actions.
247         setupAppGeneratedReplies(null /* smartReplies */);
248 
249         mNotification.actions = new Notification.Action[]{
250                 createAction("Details"),
251                 createActionBuilder("Reply").addRemoteInput(
252                         new RemoteInput.Builder("key").build()).build()
253         };
254 
255         modifyRanking(mEntry)
256                 .setSmartActions(
257                         createAction("Sys Smart Action 1"),
258                         createActionBuilder("Sys Smart Action 2")
259                                 .setContextual(true)
260                                 .setSemanticAction(Notification.Action
261                                         .SEMANTIC_ACTION_CONVERSATION_IS_PHISHING)
262                                 .build())
263                 .build();
264 
265         InflatedSmartReplyState smartReplyState =
266                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
267 
268         assertThat(smartReplyState.getSmartReplies()).isNull();
269         assertThat(smartReplyState.getSmartActions().actions)
270                 .containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
271         assertThat(smartReplyState.getSmartActions().fromAssistant).isTrue();
272         assertThat(smartReplyState.getSuppressedActions()).isNotNull();
273         assertThat(smartReplyState.getSuppressedActions().getSuppressedActionIndices())
274                 .containsExactly(1);
275         assertThat(smartReplyState.getHasPhishingAction()).isTrue();
276     }
277 
278     @Test
chooseSmartRepliesAndActions_appGenPreferredOverSysGen()279     public void chooseSmartRepliesAndActions_appGenPreferredOverSysGen() {
280         CharSequence[] appGenSmartReplies = new String[] {"Reply1", "Reply2"};
281         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
282         // replies.
283         List<Notification.Action> appGenSmartActions =
284                 createActions("Test Action 1", "Test Action 2");
285         setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions);
286 
287         modifyRanking(mEntry)
288                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
289                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
290                 .build();
291 
292         InflatedSmartReplyState smartReplyState =
293                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
294 
295         assertThat(smartReplyState.getSmartReplies().choices)
296                 .containsExactlyElementsIn(Arrays.asList(appGenSmartReplies)).inOrder();
297         assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
298         assertThat(smartReplyState.getSmartActions().actions)
299                 .containsExactlyElementsIn(appGenSmartActions).inOrder();
300         assertThat(smartReplyState.getSmartActions().fromAssistant).isFalse();
301         assertThat(smartReplyState.getSuppressedActions()).isNull();
302         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
303     }
304 
305     @Test
chooseSmartRepliesAndActions_disallowSysGenSmartActions()306     public void chooseSmartRepliesAndActions_disallowSysGenSmartActions() {
307         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
308         // actions.
309         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
310         when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false);
311 
312         modifyRanking(mEntry)
313                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
314                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
315                 .build();
316 
317         InflatedSmartReplyState smartReplyState =
318                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
319 
320         assertThat(smartReplyState.getSmartActions()).isNull();
321         assertThat(smartReplyState.getSmartReplies()).isNull();
322         assertThat(smartReplyState.getSuppressedActions()).isNull();
323         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
324     }
325 
326     @Test
chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartRepliesUnaffected()327     public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartRepliesUnaffected() {
328         when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
329         // No apps are white-listed
330         when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
331 
332         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
333         // suggestions.
334         setupAppGeneratedReplies(null /* smartReplies */);
335 
336         modifyRanking(mEntry)
337                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
338                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
339                 .build();
340 
341         InflatedSmartReplyState smartReplyState =
342                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
343 
344         assertThat(smartReplyState.getSmartReplies().choices)
345                 .containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
346         // Since no apps are allowlisted no actions should be shown.
347         assertThat(smartReplyState.getSmartActions().actions).isEmpty();
348         assertThat(smartReplyState.getSuppressedActions()).isNull();
349         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
350     }
351 
352     @Test
chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartActionsAffected()353     public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartActionsAffected() {
354         when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
355         String allowedPackage = "allowedPackage";
356         ResolveInfo allowedResolveInfo = new ResolveInfo();
357         allowedResolveInfo.activityInfo = new ActivityInfo();
358         allowedResolveInfo.activityInfo.packageName = allowedPackage;
359         when(mPackageManagerWrapper
360                 .resolveActivity(
361                         argThat(intent -> ALLOWLISTED_TEST_INTENT.getAction().equals(
362                                 intent.getAction())),
363                         anyInt() /* flags */))
364                 .thenReturn(allowedResolveInfo);
365         when(mDevicePolicyManagerWrapper.isLockTaskPermitted(allowedPackage)).thenReturn(true);
366 
367         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
368         // suggestions.
369         setupAppGeneratedReplies(null /* smartReplies */);
370         ArrayList<Notification.Action> actions = new ArrayList<>();
371         actions.add(createAction("allowed action", ALLOWLISTED_TEST_INTENT));
372         actions.add(createAction("non-allowed action", TEST_INTENT));
373 
374         modifyRanking(mEntry)
375                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
376                 .setSmartActions(actions)
377                 .build();
378 
379         InflatedSmartReplyState smartReplyState =
380                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
381 
382         // Only the action for the allowlisted package should be allowed.
383         assertThat(smartReplyState.getSmartActions().actions)
384                 .containsExactly(mEntry.getSmartActions().get(0));
385         assertThat(smartReplyState.getSuppressedActions()).isNull();
386         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
387     }
388 
389     @Test
chooseSmartRepliesAndActions_screenPinningModeEnabled_suggestionsUnaffected()390     public void chooseSmartRepliesAndActions_screenPinningModeEnabled_suggestionsUnaffected() {
391         when(mActivityManagerWrapper.isLockToAppActive()).thenReturn(true);
392         // No apps are white-listed
393         when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
394 
395         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
396         // suggestions.
397         setupAppGeneratedReplies(null /* smartReplies */);
398         modifyRanking(mEntry)
399                 .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
400                 .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
401                 .build();
402 
403         InflatedSmartReplyState smartReplyState =
404                 mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
405 
406         // We don't restrict replies or actions in screen pinning mode.
407         assertThat(smartReplyState.getSmartReplies().choices)
408                 .containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
409         assertThat(smartReplyState.getSmartActions().actions)
410                 .containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
411         assertThat(smartReplyState.getSuppressedActions()).isNull();
412         assertThat(smartReplyState.getHasPhishingAction()).isFalse();
413     }
414 
415     @Test
areSuggestionsSimilar_trueForSimilar()416     public void areSuggestionsSimilar_trueForSimilar() {
417         List<CharSequence> leftReplies = createReplies("first reply", "second reply");
418         List<CharSequence> rightReplies = createReplies("first reply", "second reply");
419         List<Notification.Action> leftActions = Arrays.asList(
420                 createAction("firstAction"),
421                 createAction("secondAction"));
422         List<Notification.Action> rightActions = Arrays.asList(
423                 createAction("firstAction"),
424                 createAction("secondAction"));
425         List<Integer> leftSuppressed = Arrays.asList(1, 2);
426         List<Integer> rightSuppressed = Arrays.asList(1, 2);
427         boolean leftPhishing = true;
428         boolean rightPhishing = true;
429 
430         InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
431                 new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
432                 new SmartActions(leftActions, false /* fromAssistant */),
433                 new SuppressedActions(leftSuppressed),
434                 leftPhishing);
435         InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
436                 new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
437                 new SmartActions(rightActions, false /* fromAssistant */),
438                 new SuppressedActions(rightSuppressed),
439                 rightPhishing);
440 
441         assertThat(SmartReplyStateInflaterKt
442                 .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
443                 .isTrue();
444     }
445 
446     @Test
areSuggestionsSimilar_falseForDifferentReplies()447     public void areSuggestionsSimilar_falseForDifferentReplies() {
448         List<CharSequence> leftReplies = createReplies("first reply");
449         List<CharSequence> rightReplies = createReplies("first reply", "second reply");
450         List<Notification.Action> leftActions = Arrays.asList(
451                 createAction("firstAction"),
452                 createAction("secondAction"));
453         List<Notification.Action> rightActions = Arrays.asList(
454                 createAction("firstAction"),
455                 createAction("secondAction"));
456         List<Integer> leftSuppressed = Arrays.asList(1, 2);
457         List<Integer> rightSuppressed = Arrays.asList(1, 2);
458         boolean leftPhishing = true;
459         boolean rightPhishing = true;
460 
461         InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
462                 new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
463                 new SmartActions(leftActions, false /* fromAssistant */),
464                 new SuppressedActions(leftSuppressed),
465                 leftPhishing);
466         InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
467                 new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
468                 new SmartActions(rightActions, false /* fromAssistant */),
469                 new SuppressedActions(rightSuppressed),
470                 rightPhishing);
471 
472         assertThat(SmartReplyStateInflaterKt
473                 .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
474                 .isFalse();
475     }
476 
477     @Test
areSuggestionsSimilar_falseForDifferentActions()478     public void areSuggestionsSimilar_falseForDifferentActions() {
479         List<CharSequence> leftReplies = createReplies("first reply", "second reply");
480         List<CharSequence> rightReplies = createReplies("first reply", "second reply");
481         List<Notification.Action> leftActions = Arrays.asList(
482                 createAction("firstAction"),
483                 createAction("secondAction"));
484         List<Notification.Action> rightActions = Arrays.asList(
485                 createAction("firstAction"),
486                 createAction("not secondAction"));
487         List<Integer> leftSuppressed = Arrays.asList(1, 2);
488         List<Integer> rightSuppressed = Arrays.asList(1, 2);
489         boolean leftPhishing = true;
490         boolean rightPhishing = true;
491 
492         InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
493                 new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
494                 new SmartActions(leftActions, false /* fromAssistant */),
495                 new SuppressedActions(leftSuppressed),
496                 leftPhishing);
497         InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
498                 new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
499                 new SmartActions(rightActions, false /* fromAssistant */),
500                 new SuppressedActions(rightSuppressed),
501                 rightPhishing);
502 
503         assertThat(SmartReplyStateInflaterKt
504                 .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
505                 .isFalse();
506     }
507 
508     @Test
areSuggestionsSimilar_falseForDifferentSuppressedActions()509     public void areSuggestionsSimilar_falseForDifferentSuppressedActions() {
510         List<CharSequence> leftReplies = createReplies("first reply", "second reply");
511         List<CharSequence> rightReplies = createReplies("first reply", "second reply");
512         List<Notification.Action> leftActions = Arrays.asList(
513                 createAction("firstAction"),
514                 createAction("secondAction"));
515         List<Notification.Action> rightActions = Arrays.asList(
516                 createAction("firstAction"),
517                 createAction("secondAction"));
518         List<Integer> leftSuppressed = Arrays.asList(1, 2);
519         List<Integer> rightSuppressed = Arrays.asList(1, 3);
520         boolean leftPhishing = true;
521         boolean rightPhishing = true;
522 
523         InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
524                 new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
525                 new SmartActions(leftActions, false /* fromAssistant */),
526                 new SuppressedActions(leftSuppressed),
527                 leftPhishing);
528         InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
529                 new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
530                 new SmartActions(rightActions, false /* fromAssistant */),
531                 new SuppressedActions(rightSuppressed),
532                 rightPhishing);
533 
534         assertThat(SmartReplyStateInflaterKt
535                 .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
536                 .isFalse();
537     }
538 
539     @Test
areSuggestionsSimilar_falseForDifferentPhishing()540     public void areSuggestionsSimilar_falseForDifferentPhishing() {
541         List<CharSequence> leftReplies = createReplies("first reply", "second reply");
542         List<CharSequence> rightReplies = createReplies("first reply", "second reply");
543         List<Notification.Action> leftActions = Arrays.asList(
544                 createAction("firstAction"),
545                 createAction("secondAction"));
546         List<Notification.Action> rightActions = Arrays.asList(
547                 createAction("firstAction"),
548                 createAction("secondAction"));
549         List<Integer> leftSuppressed = Arrays.asList(1, 2);
550         List<Integer> rightSuppressed = Arrays.asList(1, 2);
551         boolean leftPhishing = true;
552         boolean rightPhishing = false;
553 
554         InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
555                 new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
556                 new SmartActions(leftActions, false /* fromAssistant */),
557                 new SuppressedActions(leftSuppressed),
558                 leftPhishing);
559         InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
560                 new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
561                 new SmartActions(rightActions, false /* fromAssistant */),
562                 new SuppressedActions(rightSuppressed),
563                 rightPhishing);
564 
565         assertThat(SmartReplyStateInflaterKt
566                 .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
567                 .isFalse();
568     }
569 
setupAppGeneratedReplies(CharSequence[] smartReplies)570     private void setupAppGeneratedReplies(CharSequence[] smartReplies) {
571         setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
572     }
573 
setupAppGeneratedReplies( CharSequence[] smartReplies, boolean allowSystemGeneratedReplies)574     private void setupAppGeneratedReplies(
575             CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) {
576         PendingIntent pendingIntent =
577                 PendingIntent.getBroadcast(mContext, 0,
578                         TEST_INTENT.setPackage(mContext.getPackageName()),
579                         PendingIntent.FLAG_MUTABLE);
580         Notification.Action action =
581                 new Notification.Action.Builder(null, "Test Action", pendingIntent).build();
582         when(mRemoteInput.getChoices()).thenReturn(smartReplies);
583         Pair<RemoteInput, Notification.Action> remoteInputActionPair =
584                 Pair.create(mRemoteInput, action);
585         when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair);
586 
587         Notification.Action freeFormRemoteInputAction =
588                 createActionBuilder("Freeform Test Action")
589                 .setAllowGeneratedReplies(allowSystemGeneratedReplies)
590                 .build();
591         Pair<RemoteInput, Notification.Action> freeFormRemoteInputActionPair =
592                 Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction);
593         when(mNotification.findRemoteInputActionPair(true)).thenReturn(
594                 freeFormRemoteInputActionPair);
595 
596         when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false);
597     }
598 
setupAppGeneratedSuggestions( CharSequence[] smartReplies, List<Notification.Action> smartActions)599     private void setupAppGeneratedSuggestions(
600             CharSequence[] smartReplies, List<Notification.Action> smartActions) {
601         setupAppGeneratedReplies(smartReplies);
602         when(mNotification.getContextualActions()).thenReturn(smartActions);
603     }
604 
createActionBuilder(String actionTitle)605     private Notification.Action.Builder createActionBuilder(String actionTitle) {
606         return createActionBuilder(actionTitle, TEST_INTENT);
607     }
608 
createActionBuilder(String actionTitle, Intent intent)609     private Notification.Action.Builder createActionBuilder(String actionTitle, Intent intent) {
610         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
611                 intent.setPackage(mContext.getPackageName()),
612                 PendingIntent.FLAG_MUTABLE);
613         return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent);
614     }
615 
createAction(String actionTitle)616     private Notification.Action createAction(String actionTitle) {
617         return createActionBuilder(actionTitle).build();
618     }
619 
createAction(String actionTitle, Intent intent)620     private Notification.Action createAction(String actionTitle, Intent intent) {
621         return createActionBuilder(actionTitle, intent).build();
622     }
623 
createActions(String... actionTitles)624     private ArrayList<Notification.Action> createActions(String... actionTitles) {
625         ArrayList<Notification.Action> actions = new ArrayList<>();
626         for (String title : actionTitles) {
627             actions.add(createAction(title));
628         }
629         return actions;
630     }
631 
createReplies(CharSequence... replies)632     private ArrayList<CharSequence> createReplies(CharSequence... replies) {
633         return new ArrayList<>(Arrays.asList(replies));
634     }
635 }
636