blob: de1072d450f70ff2ac06baa2b96bdefd2e62b2df [file] [log] [blame]
Gustav Senntonb944ce52019-02-25 18:52:43 +00001/*
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
Gustav Sennton5a4fc212019-02-28 16:12:27 +000014 * limitations under the License. */
Gustav Senntonb944ce52019-02-25 18:52:43 +000015
16package com.android.systemui.statusbar.policy;
17
18import static com.google.common.truth.Truth.assertThat;
19
Gustav Sennton5a4fc212019-02-28 16:12:27 +000020import static org.mockito.ArgumentMatchers.anyInt;
21import static org.mockito.ArgumentMatchers.anyString;
22import static org.mockito.ArgumentMatchers.argThat;
Gustav Senntonb944ce52019-02-25 18:52:43 +000023import static org.mockito.Mockito.when;
24
25import android.app.Notification;
26import android.app.PendingIntent;
27import android.app.RemoteInput;
28import android.content.Intent;
Gustav Sennton5a4fc212019-02-28 16:12:27 +000029import android.content.pm.ActivityInfo;
30import android.content.pm.ResolveInfo;
Gustav Senntonb944ce52019-02-25 18:52:43 +000031import android.graphics.drawable.Icon;
32import android.service.notification.StatusBarNotification;
Gustav Senntonb944ce52019-02-25 18:52:43 +000033import android.util.Pair;
34
Brett Chabot84151d92019-02-27 15:37:59 -080035import androidx.test.annotation.UiThreadTest;
36import androidx.test.filters.SmallTest;
37import androidx.test.runner.AndroidJUnit4;
38
Gustav Senntonb944ce52019-02-25 18:52:43 +000039import com.android.systemui.R;
40import com.android.systemui.SysuiTestCase;
Gustav Sennton5a4fc212019-02-28 16:12:27 +000041import com.android.systemui.shared.system.ActivityManagerWrapper;
42import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
43import com.android.systemui.shared.system.PackageManagerWrapper;
Gustav Senntonb944ce52019-02-25 18:52:43 +000044import com.android.systemui.statusbar.notification.collection.NotificationEntry;
45import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
46
47import org.junit.Before;
48import org.junit.Test;
49import org.junit.runner.RunWith;
50import org.mockito.Mock;
51import org.mockito.MockitoAnnotations;
52
53import java.util.ArrayList;
54import java.util.List;
55
56@SmallTest
57@RunWith(AndroidJUnit4.class)
58public class InflatedSmartRepliesTest extends SysuiTestCase {
59
Gustav Sennton5a4fc212019-02-28 16:12:27 +000060 private static final Intent TEST_INTENT = new Intent("com.android.SMART_REPLY_VIEW_ACTION");
61 private static final Intent WHITELISTED_TEST_INTENT =
62 new Intent("com.android.WHITELISTED_TEST_ACTION");
Gustav Senntonb944ce52019-02-25 18:52:43 +000063
Gustav Sennton5a4fc212019-02-28 16:12:27 +000064 @Mock SmartReplyConstants mSmartReplyConstants;
65 @Mock StatusBarNotification mStatusBarNotification;
66 @Mock Notification mNotification;
Gustav Senntonb944ce52019-02-25 18:52:43 +000067 NotificationEntry mEntry;
Gustav Sennton5a4fc212019-02-28 16:12:27 +000068 @Mock RemoteInput mRemoteInput;
69 @Mock RemoteInput mFreeFormRemoteInput;
70 @Mock ActivityManagerWrapper mActivityManagerWrapper;
71 @Mock PackageManagerWrapper mPackageManagerWrapper;
72 @Mock DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
Gustav Senntonb944ce52019-02-25 18:52:43 +000073
74 private Icon mActionIcon;
75
76 @Before
77 @UiThreadTest
78 public void setUp() {
79 MockitoAnnotations.initMocks(this);
80
Gustav Sennton5a4fc212019-02-28 16:12:27 +000081 mDependency.injectTestDependency(ActivityManagerWrapper.class, mActivityManagerWrapper);
82 mDependency.injectTestDependency(
83 DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper);
84 mDependency.injectTestDependency(PackageManagerWrapper.class, mPackageManagerWrapper);
85
Gustav Senntonb944ce52019-02-25 18:52:43 +000086 when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true);
87 when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
88 mEntry = new NotificationEntry(mStatusBarNotification);
89 when(mSmartReplyConstants.isEnabled()).thenReturn(true);
90 mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
Gustav Sennton5a4fc212019-02-28 16:12:27 +000091
92 when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
Gustav Senntonb944ce52019-02-25 18:52:43 +000093 }
94
95 @Test
96 public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() {
97 CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
98 List<Notification.Action> smartActions =
99 createActions(new String[] {"Test Action 1", "Test Action 2"});
100 setupAppGeneratedSuggestions(smartReplies, smartActions);
101 when(mSmartReplyConstants.isEnabled()).thenReturn(false);
102
103 SmartRepliesAndActions repliesAndActions =
104 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
105
106 assertThat(repliesAndActions.smartReplies).isNull();
107 assertThat(repliesAndActions.smartActions).isNull();
108 }
109
110 @Test
111 public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() {
112 mEntry.systemGeneratedSmartReplies =
113 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
114 mEntry.systemGeneratedSmartActions =
115 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
116 when(mSmartReplyConstants.isEnabled()).thenReturn(false);
117
118 SmartRepliesAndActions repliesAndActions =
119 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
120
121 assertThat(repliesAndActions.smartReplies).isNull();
122 assertThat(repliesAndActions.smartActions).isNull();
123 }
124
125 @Test
126 public void chooseSmartRepliesAndActions_appGeneratedSmartReplies() {
127 CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
128 setupAppGeneratedReplies(smartReplies);
129
130 SmartRepliesAndActions repliesAndActions =
131 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
132
133 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies);
134 assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
135 assertThat(repliesAndActions.smartActions).isNull();
136 }
137
138 @Test
139 public void chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions() {
140 CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
141 List<Notification.Action> smartActions =
142 createActions(new String[] {"Test Action 1", "Test Action 2"});
143 setupAppGeneratedSuggestions(smartReplies, smartActions);
144
145 SmartRepliesAndActions repliesAndActions =
146 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
147
148 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies);
149 assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
150 assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions);
151 assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
152 }
153
154 @Test
155 public void chooseSmartRepliesAndActions_sysGeneratedSmartReplies() {
156 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
157 // replies.
158 setupAppGeneratedReplies(null /* smartReplies */);
159
160 mEntry.systemGeneratedSmartReplies =
161 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
162 SmartRepliesAndActions repliesAndActions =
163 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
164
165 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
166 mEntry.systemGeneratedSmartReplies);
167 assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue();
168 assertThat(repliesAndActions.smartActions).isNull();
169 }
170
171 @Test
172 public void chooseSmartRepliesAndActions_noSysGeneratedSmartRepliesIfNotAllowed() {
173 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
174 // replies.
175 setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
176
177 mEntry.systemGeneratedSmartReplies =
178 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
179 SmartRepliesAndActions repliesAndActions =
180 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
181
182 assertThat(repliesAndActions.smartReplies).isNull();
183 assertThat(repliesAndActions.smartActions).isNull();
184 }
185
186 @Test
187 public void chooseSmartRepliesAndActions_sysGeneratedSmartActions() {
188 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
189 // actions.
190 setupAppGeneratedReplies(null /* smartReplies */);
191
192 mEntry.systemGeneratedSmartActions =
193 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
194 SmartRepliesAndActions repliesAndActions =
195 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
196
197 assertThat(repliesAndActions.smartReplies).isNull();
198 assertThat(repliesAndActions.smartActions.actions)
199 .isEqualTo(mEntry.systemGeneratedSmartActions);
200 assertThat(repliesAndActions.smartActions.fromAssistant).isTrue();
201 }
202
203 @Test
204 public void chooseSmartRepliesAndActions_appGenPreferredOverSysGen() {
205 CharSequence[] appGenSmartReplies = new String[] {"Reply1", "Reply2"};
206 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
207 // replies.
208 List<Notification.Action> appGenSmartActions =
209 createActions(new String[] {"Test Action 1", "Test Action 2"});
210 setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions);
211
212 mEntry.systemGeneratedSmartReplies =
213 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
214 mEntry.systemGeneratedSmartActions =
215 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
216
217 SmartRepliesAndActions repliesAndActions =
218 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
219
220 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(appGenSmartReplies);
221 assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
222 assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions);
223 assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
224 }
225
226 @Test
227 public void chooseSmartRepliesAndActions_disallowSysGenSmartActions() {
228 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
229 // actions.
230 setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
231 when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false);
232 mEntry.systemGeneratedSmartReplies =
233 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
234 mEntry.systemGeneratedSmartActions =
235 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
236
237 SmartRepliesAndActions repliesAndActions =
238 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
239
240 assertThat(repliesAndActions.smartActions).isNull();
241 assertThat(repliesAndActions.smartReplies).isNull();
242 }
243
Gustav Sennton5a4fc212019-02-28 16:12:27 +0000244 @Test
245 public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartRepliesUnaffected() {
246 when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
247 // No apps are white-listed
248 when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
249
250 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
251 // suggestions.
252 setupAppGeneratedReplies(null /* smartReplies */);
253 mEntry.systemGeneratedSmartReplies =
254 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
255 mEntry.systemGeneratedSmartActions =
256 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
257
258 SmartRepliesAndActions repliesAndActions =
259 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
260
261 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
262 mEntry.systemGeneratedSmartReplies);
263 // Since no apps are whitelisted no actions should be shown.
264 assertThat(repliesAndActions.smartActions.actions).isEmpty();
265 }
266
267 @Test
268 public void chooseSmartRepliesAndActions_lockTaskKioskModeEnabled_smartActionsAffected() {
269 when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(true);
270 String allowedPackage = "allowedPackage";
271 ResolveInfo allowedResolveInfo = new ResolveInfo();
272 allowedResolveInfo.activityInfo = new ActivityInfo();
273 allowedResolveInfo.activityInfo.packageName = allowedPackage;
274 when(mPackageManagerWrapper
275 .resolveActivity(
276 argThat(intent -> WHITELISTED_TEST_INTENT.getAction().equals(
277 intent.getAction())),
278 anyInt() /* flags */))
279 .thenReturn(allowedResolveInfo);
280 when(mDevicePolicyManagerWrapper.isLockTaskPermitted(allowedPackage)).thenReturn(true);
281
282 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
283 // suggestions.
284 setupAppGeneratedReplies(null /* smartReplies */);
285 mEntry.systemGeneratedSmartReplies =
286 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
287 List<Notification.Action> actions = new ArrayList<>();
288 actions.add(createAction("allowed action", WHITELISTED_TEST_INTENT));
289 actions.add(createAction("non-allowed action", TEST_INTENT));
290 mEntry.systemGeneratedSmartActions = actions;
291
292 SmartRepliesAndActions repliesAndActions =
293 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
294
295 // Only the action for the whitelisted package should be allowed.
296 assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
297 assertThat(repliesAndActions.smartActions.actions.get(0)).isEqualTo(
298 mEntry.systemGeneratedSmartActions.get(0));
299 }
300
301 @Test
302 public void chooseSmartRepliesAndActions_screenPinningModeEnabled_suggestionsUnaffected() {
303 when(mActivityManagerWrapper.isLockToAppActive()).thenReturn(true);
304 // No apps are white-listed
305 when(mDevicePolicyManagerWrapper.isLockTaskPermitted(anyString())).thenReturn(false);
306
307 // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
308 // suggestions.
309 setupAppGeneratedReplies(null /* smartReplies */);
310 mEntry.systemGeneratedSmartReplies =
311 new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
312 mEntry.systemGeneratedSmartActions =
313 createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
314
315 SmartRepliesAndActions repliesAndActions =
316 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
317
318 // We don't restrict replies or actions in screen pinning mode.
319 assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
320 mEntry.systemGeneratedSmartReplies);
321 assertThat(repliesAndActions.smartActions.actions).isEqualTo(
322 mEntry.systemGeneratedSmartActions);
323 }
324
Gustav Senntonb944ce52019-02-25 18:52:43 +0000325 private void setupAppGeneratedReplies(CharSequence[] smartReplies) {
326 setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
327 }
328
329 private void setupAppGeneratedReplies(
330 CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) {
331 PendingIntent pendingIntent =
Gustav Sennton5a4fc212019-02-28 16:12:27 +0000332 PendingIntent.getBroadcast(mContext, 0, TEST_INTENT, 0);
Gustav Senntonb944ce52019-02-25 18:52:43 +0000333 Notification.Action action =
334 new Notification.Action.Builder(null, "Test Action", pendingIntent).build();
335 when(mRemoteInput.getChoices()).thenReturn(smartReplies);
336 Pair<RemoteInput, Notification.Action> remoteInputActionPair =
337 Pair.create(mRemoteInput, action);
338 when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair);
339
340 Notification.Action freeFormRemoteInputAction =
341 createActionBuilder("Freeform Test Action")
342 .setAllowGeneratedReplies(allowSystemGeneratedReplies)
343 .build();
344 Pair<RemoteInput, Notification.Action> freeFormRemoteInputActionPair =
345 Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction);
346 when(mNotification.findRemoteInputActionPair(true)).thenReturn(
347 freeFormRemoteInputActionPair);
348
349 when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false);
350 }
351
352 private void setupAppGeneratedSuggestions(
353 CharSequence[] smartReplies, List<Notification.Action> smartActions) {
354 setupAppGeneratedReplies(smartReplies);
355 when(mNotification.getContextualActions()).thenReturn(smartActions);
356 }
357
358 private Notification.Action.Builder createActionBuilder(String actionTitle) {
Gustav Sennton5a4fc212019-02-28 16:12:27 +0000359 return createActionBuilder(actionTitle, TEST_INTENT);
360 }
361
362 private Notification.Action.Builder createActionBuilder(String actionTitle, Intent intent) {
363 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
Gustav Senntonb944ce52019-02-25 18:52:43 +0000364 return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent);
365 }
366
367 private Notification.Action createAction(String actionTitle) {
368 return createActionBuilder(actionTitle).build();
369 }
370
Gustav Sennton5a4fc212019-02-28 16:12:27 +0000371 private Notification.Action createAction(String actionTitle, Intent intent) {
372 return createActionBuilder(actionTitle, intent).build();
373 }
374
Gustav Senntonb944ce52019-02-25 18:52:43 +0000375 private List<Notification.Action> createActions(String[] actionTitles) {
376 List<Notification.Action> actions = new ArrayList<>();
377 for (String title : actionTitles) {
378 actions.add(createAction(title));
379 }
380 return actions;
381 }
382}