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