blob: cb4470347826630c98148e370360c7e133e039f5 [file] [log] [blame]
Kevin Han225b7122020-01-31 15:42:42 -08001/*
2 * Copyright (C) 2020 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
17package com.android.systemui.statusbar.notification.row;
18
19import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20
21import static junit.framework.Assert.assertNotNull;
22
23import static org.mockito.ArgumentMatchers.any;
24import static org.mockito.ArgumentMatchers.eq;
25import static org.mockito.Mockito.mock;
26import static org.mockito.Mockito.never;
27import static org.mockito.Mockito.verify;
28import static org.mockito.Mockito.when;
29
30import android.app.Notification;
Ned Burnsd8b51542020-03-13 20:52:43 -040031import android.content.pm.LauncherApps;
Kevin Han225b7122020-01-31 15:42:42 -080032import android.os.Handler;
33import android.service.notification.NotificationListenerService;
34import android.service.notification.NotificationListenerService.Ranking;
35import android.service.notification.StatusBarNotification;
36import android.testing.AndroidTestingRunner;
37import android.testing.TestableLooper;
38
39import androidx.test.filters.SmallTest;
40
41import com.android.internal.util.NotificationMessagingUtil;
42import com.android.systemui.R;
43import com.android.systemui.SysuiTestCase;
44import com.android.systemui.plugins.FalsingManager;
45import com.android.systemui.plugins.statusbar.StatusBarStateController;
46import com.android.systemui.shared.plugins.PluginManager;
47import com.android.systemui.statusbar.FeatureFlags;
48import com.android.systemui.statusbar.NotificationLockscreenUserManager;
49import com.android.systemui.statusbar.NotificationMediaManager;
50import com.android.systemui.statusbar.NotificationPresenter;
51import com.android.systemui.statusbar.NotificationRemoteInputManager;
52import com.android.systemui.statusbar.SbnBuilder;
53import com.android.systemui.statusbar.SmartReplyController;
54import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
55import com.android.systemui.statusbar.notification.NotificationClicker;
56import com.android.systemui.statusbar.notification.NotificationEntryListener;
57import com.android.systemui.statusbar.notification.NotificationEntryManager;
58import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
59import com.android.systemui.statusbar.notification.NotificationFilter;
Kevin Han225b7122020-01-31 15:42:42 -080060import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
61import com.android.systemui.statusbar.notification.collection.NotificationEntry;
62import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
63import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
64import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
Ned Burnsd8b51542020-03-13 20:52:43 -040065import com.android.systemui.statusbar.notification.icon.IconBuilder;
66import com.android.systemui.statusbar.notification.icon.IconManager;
Beverly Taid1e175c2020-03-10 16:37:04 +000067import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
Kevin Han225b7122020-01-31 15:42:42 -080068import com.android.systemui.statusbar.notification.logging.NotificationLogger;
69import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
Kevin Hanc5711872020-03-04 22:23:02 +000070import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
Kevin Han225b7122020-01-31 15:42:42 -080071import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
72import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
73import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
74import com.android.systemui.statusbar.phone.KeyguardBypassController;
75import com.android.systemui.statusbar.phone.NotificationGroupManager;
76import com.android.systemui.statusbar.policy.HeadsUpManager;
77import com.android.systemui.statusbar.policy.SmartReplyConstants;
78import com.android.systemui.util.leak.LeakDetector;
79import com.android.systemui.util.time.FakeSystemClock;
80
81import org.junit.After;
82import org.junit.Before;
Kevin Hanc5711872020-03-04 22:23:02 +000083import org.junit.Ignore;
Kevin Han225b7122020-01-31 15:42:42 -080084import org.junit.Test;
85import org.junit.runner.RunWith;
86import org.mockito.ArgumentCaptor;
87import org.mockito.Mock;
88import org.mockito.Mockito;
89import org.mockito.MockitoAnnotations;
90import org.mockito.stubbing.Answer;
91
92/**
93 * Functional tests for notification inflation from {@link NotificationEntryManager}.
94 */
95@SmallTest
Kevin Hanc5711872020-03-04 22:23:02 +000096@Ignore("Flaking")
Kevin Han225b7122020-01-31 15:42:42 -080097@RunWith(AndroidTestingRunner.class)
98@TestableLooper.RunWithLooper(setAsMainLooper = true)
99public class NotificationEntryManagerInflationTest extends SysuiTestCase {
100
101 private static final String TEST_TITLE = "Title";
102 private static final String TEST_TEXT = "Text";
103 private static final long TIMEOUT_TIME = 10000;
104 private static final Runnable TIMEOUT_RUNNABLE = () -> {
105 throw new RuntimeException("Timed out waiting to inflate");
106 };
107
108 @Mock private NotificationPresenter mPresenter;
109 @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
110 @Mock private NotificationListContainer mListContainer;
111 @Mock private NotificationEntryListener mEntryListener;
112 @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
113 @Mock private HeadsUpManager mHeadsUpManager;
Beverly Taid1e175c2020-03-10 16:37:04 +0000114 @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
Kevin Han225b7122020-01-31 15:42:42 -0800115 @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
116 @Mock private NotificationGutsManager mGutsManager;
117 @Mock private NotificationRemoteInputManager mRemoteInputManager;
118 @Mock private NotificationMediaManager mNotificationMediaManager;
119 @Mock private ExpandableNotificationRowComponent.Builder
120 mExpandableNotificationRowComponentBuilder;
121 @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
122 @Mock private FalsingManager mFalsingManager;
123 @Mock private KeyguardBypassController mKeyguardBypassController;
124 @Mock private StatusBarStateController mStatusBarStateController;
125
126 @Mock private NotificationGroupManager mGroupManager;
127 @Mock private FeatureFlags mFeatureFlags;
128 @Mock private LeakDetector mLeakDetector;
129
130 @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
131 @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
132
133 private StatusBarNotification mSbn;
134 private NotificationListenerService.RankingMap mRankingMap;
135 private NotificationEntryManager mEntryManager;
136 private NotificationRowBinderImpl mRowBinder;
137 private Handler mHandler;
138
139 @Before
140 public void setUp() {
141 MockitoAnnotations.initMocks(this);
142 mDependency.injectMockDependency(SmartReplyController.class);
143
144 mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
145
146 Notification notification = new Notification.Builder(mContext)
147 .setSmallIcon(R.drawable.ic_person)
148 .setContentTitle(TEST_TITLE)
149 .setContentText(TEST_TEXT)
150 .build();
151 mSbn = new SbnBuilder()
152 .setNotification(notification)
153 .build();
154
155 when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
156 when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
157
158 mEntryManager = new NotificationEntryManager(
159 mock(NotificationEntryManagerLogger.class),
160 mGroupManager,
161 new NotificationRankingManager(
162 () -> mock(NotificationMediaManager.class),
163 mGroupManager,
164 mHeadsUpManager,
165 mock(NotificationFilter.class),
166 mock(NotificationEntryManagerLogger.class),
167 mock(NotificationSectionsFeatureManager.class),
168 mock(PeopleNotificationIdentifier.class),
169 mock(HighPriorityProvider.class)),
170 mEnvironment,
171 mFeatureFlags,
172 () -> mRowBinder,
173 () -> mRemoteInputManager,
174 mLeakDetector,
175 mock(ForegroundServiceDismissalFeatureController.class)
176 );
177
178 NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
179 NotifBindPipeline pipeline = new NotifBindPipeline(
180 mEntryManager,
181 mock(NotifBindPipelineLogger.class));
182 NotificationContentInflater binder = new NotificationContentInflater(
183 cache,
184 mRemoteInputManager,
185 () -> mock(SmartReplyConstants.class),
186 () -> mock(SmartReplyController.class));
187 RowContentBindStage stage = new RowContentBindStage(
188 binder,
189 mock(NotifInflationErrorManager.class),
190 mock(RowContentBindStageLogger.class));
191 pipeline.setStage(stage);
192
193 ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
194 ArgumentCaptor.forClass(ExpandableNotificationRow.class);
195 when(mExpandableNotificationRowComponentBuilder
196 .expandableNotificationRow(viewCaptor.capture()))
197 .thenReturn(mExpandableNotificationRowComponentBuilder);
198 when(mExpandableNotificationRowComponentBuilder
199 .notificationEntry(any()))
200 .thenReturn(mExpandableNotificationRowComponentBuilder);
201 when(mExpandableNotificationRowComponentBuilder
202 .onDismissRunnable(any()))
203 .thenReturn(mExpandableNotificationRowComponentBuilder);
204 when(mExpandableNotificationRowComponentBuilder
205 .inflationCallback(any()))
206 .thenReturn(mExpandableNotificationRowComponentBuilder);
207 when(mExpandableNotificationRowComponentBuilder
208 .rowContentBindStage(any()))
209 .thenReturn(mExpandableNotificationRowComponentBuilder);
210 when(mExpandableNotificationRowComponentBuilder
211 .onExpandClickListener(any()))
212 .thenReturn(mExpandableNotificationRowComponentBuilder);
213
214 when(mExpandableNotificationRowComponentBuilder.build())
215 .thenReturn(mExpandableNotificationRowComponent);
216 when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
217 .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
218 new ExpandableNotificationRowController(
219 viewCaptor.getValue(),
220 mock(ActivatableNotificationViewController.class),
221 mNotificationMediaManager,
222 mock(PluginManager.class),
223 new FakeSystemClock(),
224 "FOOBAR", "FOOBAR",
225 mKeyguardBypassController,
226 mGroupManager,
227 stage,
228 mock(NotificationLogger.class),
229 mHeadsUpManager,
230 mPresenter,
231 mStatusBarStateController,
232 mEntryManager,
233 mGutsManager,
234 true,
235 null,
236 mFalsingManager
237 ));
238
239 when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
240 .thenReturn(mNotificationRowComponentBuilder);
241 when(mNotificationRowComponentBuilder.build()).thenReturn(
242 () -> mActivatableNotificationViewController);
243
244 mRowBinder = new NotificationRowBinderImpl(
245 mContext,
246 new NotificationMessagingUtil(mContext),
247 mRemoteInputManager,
248 mLockscreenUserManager,
249 pipeline,
250 stage,
Kevin Han225b7122020-01-31 15:42:42 -0800251 mNotificationInterruptionStateProvider,
252 RowInflaterTask::new,
Ned Burnsd8b51542020-03-13 20:52:43 -0400253 mExpandableNotificationRowComponentBuilder,
254 new IconManager(
255 mEntryManager,
256 mock(LauncherApps.class),
257 new IconBuilder(mContext)));
Kevin Han225b7122020-01-31 15:42:42 -0800258
259 mEntryManager.setUpWithPresenter(mPresenter);
260 mEntryManager.addNotificationEntryListener(mEntryListener);
261
262 mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
263 mRowBinder.setInflationCallback(mEntryManager);
264 mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
265
266 Ranking ranking = new Ranking();
267 ranking.populate(
268 mSbn.getKey(),
269 0,
270 false,
271 0,
272 0,
273 IMPORTANCE_DEFAULT,
274 null,
275 null,
276 null,
277 null,
278 null,
279 true,
280 Ranking.USER_SENTIMENT_NEUTRAL,
281 false,
282 -1,
283 false,
284 null,
285 null,
286 false,
287 false,
Julia Reynolds138111f2020-02-26 11:17:39 -0500288 false,
289 null);
Kevin Han225b7122020-01-31 15:42:42 -0800290 mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
291
292 TestableLooper.get(this).processAllMessages();
293 }
294
295 @After
296 public void cleanUp() {
297 // Don't leave anything on main thread
298 TestableLooper.get(this).processAllMessages();
299 }
300
301 @Test
302 public void testAddNotification() {
303 // WHEN a notification is added
304 mEntryManager.addNotification(mSbn, mRankingMap);
305 ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
306 NotificationEntry.class);
307 verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
308 NotificationEntry entry = entryCaptor.getValue();
309
Kevin Hanc5711872020-03-04 22:23:02 +0000310 // Wait for inflation
311 // row inflation, system notification, remote views, contracted view
312 waitForMessages(4);
Kevin Han225b7122020-01-31 15:42:42 -0800313
314 // THEN the notification has its row inflated
315 assertNotNull(entry.getRow());
316 assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
317
318 // THEN inflation callbacks are called
319 verify(mBindCallback).onBindRow(eq(entry), any(), eq(mSbn), any());
320 verify(mEntryListener, never()).onInflationError(any(), any());
321 verify(mEntryListener).onEntryInflated(entry);
322 verify(mEntryListener).onNotificationAdded(entry);
323
324 // THEN the notification is active
325 assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
326
327 // THEN we update the presenter
328 verify(mPresenter).updateNotificationViews();
329 }
330
331 @Test
332 public void testUpdateNotification() {
333 // GIVEN a notification already added
334 mEntryManager.addNotification(mSbn, mRankingMap);
335 ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
336 NotificationEntry.class);
337 verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
338 NotificationEntry entry = entryCaptor.getValue();
Kevin Hanc5711872020-03-04 22:23:02 +0000339 waitForMessages(4);
Kevin Han225b7122020-01-31 15:42:42 -0800340
341 Mockito.reset(mEntryListener);
342 Mockito.reset(mPresenter);
343
344 // WHEN the notification is updated
345 mEntryManager.updateNotification(mSbn, mRankingMap);
346
Kevin Hanc5711872020-03-04 22:23:02 +0000347 // Wait for inflation
348 // remote views, contracted view
349 waitForMessages(2);
Kevin Han225b7122020-01-31 15:42:42 -0800350
351 // THEN the notification has its row and inflated
352 assertNotNull(entry.getRow());
353
354 // THEN inflation callbacks are called
355 verify(mEntryListener, never()).onInflationError(any(), any());
356 verify(mEntryListener).onEntryReinflated(entry);
357
358 // THEN we update the presenter
359 verify(mPresenter).updateNotificationViews();
360 }
361
Kevin Hanc5711872020-03-04 22:23:02 +0000362 /**
363 * Wait for a certain number of messages to finish before continuing, timing out if they never
364 * occur.
365 *
366 * As part of the inflation pipeline, the main thread is forced to deal with several callbacks
367 * due to the nature of the API used (generally because they're {@link android.os.AsyncTask}
368 * callbacks). In order, these are
369 *
370 * 1) Callback after row inflation. See {@link RowInflaterTask}.
371 * 2) Callback checking if row is system notification. See
372 * {@link ExpandableNotificationRow#setEntry}
373 * 3) Callback after remote views are created. See
374 * {@link NotificationContentInflater.AsyncInflationTask}.
375 * 4-6) Callback after each content view is inflated/rebound from remote view. See
376 * {@link NotificationContentInflater#applyRemoteView} and {@link InflationFlag}.
377 *
378 * Depending on the test, only some of these will be necessary. For example, generally, not
379 * every content view is inflated or the row may not be inflated if one already exists.
380 *
381 * Currently, the burden is on the developer to figure these out until we have a much more
382 * test-friendly way of executing inflation logic (i.e. pass in an executor).
383 */
384 private void waitForMessages(int numMessages) {
Kevin Han225b7122020-01-31 15:42:42 -0800385 mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
Kevin Hanc5711872020-03-04 22:23:02 +0000386 TestableLooper.get(this).processMessages(numMessages);
Kevin Han225b7122020-01-31 15:42:42 -0800387 mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
Kevin Han225b7122020-01-31 15:42:42 -0800388 }
389
390}