blob: 2894abb8f36499e805a57672443f93cac4e54682 [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
Kevin Han39ce15f2020-04-07 18:13:36 -070021import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
22
Kevin Han225b7122020-01-31 15:42:42 -080023import static junit.framework.Assert.assertNotNull;
24
25import static org.mockito.ArgumentMatchers.any;
Kevin Han225b7122020-01-31 15:42:42 -080026import static org.mockito.Mockito.mock;
27import static org.mockito.Mockito.never;
28import static org.mockito.Mockito.verify;
29import static org.mockito.Mockito.when;
30
31import android.app.Notification;
Ned Burnsd8b51542020-03-13 20:52:43 -040032import android.content.pm.LauncherApps;
Kevin Han225b7122020-01-31 15:42:42 -080033import android.os.Handler;
34import android.service.notification.NotificationListenerService;
35import android.service.notification.NotificationListenerService.Ranking;
36import android.service.notification.StatusBarNotification;
37import android.testing.AndroidTestingRunner;
38import android.testing.TestableLooper;
39
Kevin Hana6d5bb42020-03-17 13:55:49 -070040import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
Kevin Han225b7122020-01-31 15:42:42 -080041import androidx.test.filters.SmallTest;
42
43import com.android.internal.util.NotificationMessagingUtil;
44import com.android.systemui.R;
45import com.android.systemui.SysuiTestCase;
46import com.android.systemui.plugins.FalsingManager;
47import com.android.systemui.plugins.statusbar.StatusBarStateController;
48import com.android.systemui.shared.plugins.PluginManager;
49import com.android.systemui.statusbar.FeatureFlags;
50import com.android.systemui.statusbar.NotificationLockscreenUserManager;
51import com.android.systemui.statusbar.NotificationMediaManager;
52import com.android.systemui.statusbar.NotificationPresenter;
53import com.android.systemui.statusbar.NotificationRemoteInputManager;
54import com.android.systemui.statusbar.SbnBuilder;
55import com.android.systemui.statusbar.SmartReplyController;
Steve Elliott46bb2a12020-03-17 11:04:09 -040056import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
Kevin Han225b7122020-01-31 15:42:42 -080057import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
58import com.android.systemui.statusbar.notification.NotificationClicker;
59import com.android.systemui.statusbar.notification.NotificationEntryListener;
60import com.android.systemui.statusbar.notification.NotificationEntryManager;
61import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
62import com.android.systemui.statusbar.notification.NotificationFilter;
Kevin Han225b7122020-01-31 15:42:42 -080063import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
64import com.android.systemui.statusbar.notification.collection.NotificationEntry;
65import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
Kevin Hanf69b71e2020-04-13 15:46:14 -070066import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
Kevin Han225b7122020-01-31 15:42:42 -080067import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
68import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
Ned Burnsd8b51542020-03-13 20:52:43 -040069import com.android.systemui.statusbar.notification.icon.IconBuilder;
70import com.android.systemui.statusbar.notification.icon.IconManager;
Beverly Taid1e175c2020-03-10 16:37:04 +000071import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
Kevin Han225b7122020-01-31 15:42:42 -080072import com.android.systemui.statusbar.notification.logging.NotificationLogger;
73import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
Kevin Han225b7122020-01-31 15:42:42 -080074import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
75import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
76import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
77import com.android.systemui.statusbar.phone.KeyguardBypassController;
78import com.android.systemui.statusbar.phone.NotificationGroupManager;
79import com.android.systemui.statusbar.policy.HeadsUpManager;
80import com.android.systemui.statusbar.policy.SmartReplyConstants;
Kevin Hana6d5bb42020-03-17 13:55:49 -070081import com.android.systemui.util.concurrency.FakeExecutor;
Kevin Han225b7122020-01-31 15:42:42 -080082import com.android.systemui.util.leak.LeakDetector;
83import com.android.systemui.util.time.FakeSystemClock;
84
85import org.junit.After;
86import org.junit.Before;
87import org.junit.Test;
88import org.junit.runner.RunWith;
89import org.mockito.ArgumentCaptor;
90import org.mockito.Mock;
91import org.mockito.Mockito;
92import org.mockito.MockitoAnnotations;
93import org.mockito.stubbing.Answer;
94
Kevin Hana6d5bb42020-03-17 13:55:49 -070095import java.util.concurrent.CountDownLatch;
96
Kevin Han225b7122020-01-31 15:42:42 -080097/**
98 * Functional tests for notification inflation from {@link NotificationEntryManager}.
99 */
100@SmallTest
Kevin Han225b7122020-01-31 15:42:42 -0800101@RunWith(AndroidTestingRunner.class)
102@TestableLooper.RunWithLooper(setAsMainLooper = true)
103public class NotificationEntryManagerInflationTest extends SysuiTestCase {
104
105 private static final String TEST_TITLE = "Title";
106 private static final String TEST_TEXT = "Text";
107 private static final long TIMEOUT_TIME = 10000;
108 private static final Runnable TIMEOUT_RUNNABLE = () -> {
109 throw new RuntimeException("Timed out waiting to inflate");
110 };
111
112 @Mock private NotificationPresenter mPresenter;
113 @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
114 @Mock private NotificationListContainer mListContainer;
115 @Mock private NotificationEntryListener mEntryListener;
116 @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
117 @Mock private HeadsUpManager mHeadsUpManager;
Beverly Taid1e175c2020-03-10 16:37:04 +0000118 @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
Kevin Han225b7122020-01-31 15:42:42 -0800119 @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
120 @Mock private NotificationGutsManager mGutsManager;
121 @Mock private NotificationRemoteInputManager mRemoteInputManager;
122 @Mock private NotificationMediaManager mNotificationMediaManager;
123 @Mock private ExpandableNotificationRowComponent.Builder
124 mExpandableNotificationRowComponentBuilder;
125 @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
126 @Mock private FalsingManager mFalsingManager;
127 @Mock private KeyguardBypassController mKeyguardBypassController;
128 @Mock private StatusBarStateController mStatusBarStateController;
129
130 @Mock private NotificationGroupManager mGroupManager;
131 @Mock private FeatureFlags mFeatureFlags;
132 @Mock private LeakDetector mLeakDetector;
133
134 @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
135 @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
Steve Elliott2304e4a2020-04-01 17:03:41 -0400136 @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
Kevin Han225b7122020-01-31 15:42:42 -0800137
138 private StatusBarNotification mSbn;
139 private NotificationListenerService.RankingMap mRankingMap;
140 private NotificationEntryManager mEntryManager;
141 private NotificationRowBinderImpl mRowBinder;
142 private Handler mHandler;
Kevin Hana6d5bb42020-03-17 13:55:49 -0700143 private FakeExecutor mBgExecutor;
Kevin Han39ce15f2020-04-07 18:13:36 -0700144 private RowContentBindStage mRowContentBindStage;
Kevin Han225b7122020-01-31 15:42:42 -0800145
146 @Before
147 public void setUp() {
148 MockitoAnnotations.initMocks(this);
149 mDependency.injectMockDependency(SmartReplyController.class);
150
151 mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
152
Kevin Han39ce15f2020-04-07 18:13:36 -0700153 // Add an action so heads up content views are made
154 Notification.Action action = new Notification.Action.Builder(null, null, null).build();
Kevin Han225b7122020-01-31 15:42:42 -0800155 Notification notification = new Notification.Builder(mContext)
156 .setSmallIcon(R.drawable.ic_person)
157 .setContentTitle(TEST_TITLE)
158 .setContentText(TEST_TEXT)
Kevin Han39ce15f2020-04-07 18:13:36 -0700159 .setActions(action)
Kevin Han225b7122020-01-31 15:42:42 -0800160 .build();
161 mSbn = new SbnBuilder()
162 .setNotification(notification)
163 .build();
164
165 when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
166 when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
167
168 mEntryManager = new NotificationEntryManager(
169 mock(NotificationEntryManagerLogger.class),
170 mGroupManager,
171 new NotificationRankingManager(
172 () -> mock(NotificationMediaManager.class),
173 mGroupManager,
174 mHeadsUpManager,
175 mock(NotificationFilter.class),
176 mock(NotificationEntryManagerLogger.class),
177 mock(NotificationSectionsFeatureManager.class),
178 mock(PeopleNotificationIdentifier.class),
179 mock(HighPriorityProvider.class)),
180 mEnvironment,
181 mFeatureFlags,
182 () -> mRowBinder,
183 () -> mRemoteInputManager,
184 mLeakDetector,
185 mock(ForegroundServiceDismissalFeatureController.class)
186 );
187
188 NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
189 NotifBindPipeline pipeline = new NotifBindPipeline(
190 mEntryManager,
Kevin Hana7c21be2020-04-01 17:58:35 -0700191 mock(NotifBindPipelineLogger.class),
192 TestableLooper.get(this).getLooper());
Kevin Hana6d5bb42020-03-17 13:55:49 -0700193 mBgExecutor = new FakeExecutor(new FakeSystemClock());
Kevin Han225b7122020-01-31 15:42:42 -0800194 NotificationContentInflater binder = new NotificationContentInflater(
195 cache,
196 mRemoteInputManager,
197 () -> mock(SmartReplyConstants.class),
Steve Elliott46bb2a12020-03-17 11:04:09 -0400198 () -> mock(SmartReplyController.class),
Kevin Hana6d5bb42020-03-17 13:55:49 -0700199 mock(ConversationNotificationProcessor.class),
200 mBgExecutor);
Kevin Han39ce15f2020-04-07 18:13:36 -0700201 mRowContentBindStage = new RowContentBindStage(
Kevin Han225b7122020-01-31 15:42:42 -0800202 binder,
203 mock(NotifInflationErrorManager.class),
204 mock(RowContentBindStageLogger.class));
Kevin Han39ce15f2020-04-07 18:13:36 -0700205 pipeline.setStage(mRowContentBindStage);
Kevin Han225b7122020-01-31 15:42:42 -0800206
207 ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
208 ArgumentCaptor.forClass(ExpandableNotificationRow.class);
209 when(mExpandableNotificationRowComponentBuilder
210 .expandableNotificationRow(viewCaptor.capture()))
211 .thenReturn(mExpandableNotificationRowComponentBuilder);
212 when(mExpandableNotificationRowComponentBuilder
213 .notificationEntry(any()))
214 .thenReturn(mExpandableNotificationRowComponentBuilder);
215 when(mExpandableNotificationRowComponentBuilder
216 .onDismissRunnable(any()))
217 .thenReturn(mExpandableNotificationRowComponentBuilder);
218 when(mExpandableNotificationRowComponentBuilder
Kevin Han225b7122020-01-31 15:42:42 -0800219 .rowContentBindStage(any()))
220 .thenReturn(mExpandableNotificationRowComponentBuilder);
221 when(mExpandableNotificationRowComponentBuilder
222 .onExpandClickListener(any()))
223 .thenReturn(mExpandableNotificationRowComponentBuilder);
224
225 when(mExpandableNotificationRowComponentBuilder.build())
226 .thenReturn(mExpandableNotificationRowComponent);
227 when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
228 .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
229 new ExpandableNotificationRowController(
230 viewCaptor.getValue(),
231 mock(ActivatableNotificationViewController.class),
232 mNotificationMediaManager,
233 mock(PluginManager.class),
234 new FakeSystemClock(),
235 "FOOBAR", "FOOBAR",
236 mKeyguardBypassController,
237 mGroupManager,
Kevin Han39ce15f2020-04-07 18:13:36 -0700238 mRowContentBindStage,
Kevin Han225b7122020-01-31 15:42:42 -0800239 mock(NotificationLogger.class),
240 mHeadsUpManager,
241 mPresenter,
242 mStatusBarStateController,
Kevin Han225b7122020-01-31 15:42:42 -0800243 mGutsManager,
244 true,
245 null,
Steve Elliott2304e4a2020-04-01 17:03:41 -0400246 mFalsingManager,
247 mPeopleNotificationIdentifier
Kevin Han225b7122020-01-31 15:42:42 -0800248 ));
249
250 when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
251 .thenReturn(mNotificationRowComponentBuilder);
252 when(mNotificationRowComponentBuilder.build()).thenReturn(
253 () -> mActivatableNotificationViewController);
254
255 mRowBinder = new NotificationRowBinderImpl(
256 mContext,
257 new NotificationMessagingUtil(mContext),
258 mRemoteInputManager,
259 mLockscreenUserManager,
260 pipeline,
Kevin Han39ce15f2020-04-07 18:13:36 -0700261 mRowContentBindStage,
Kevin Han225b7122020-01-31 15:42:42 -0800262 mNotificationInterruptionStateProvider,
263 RowInflaterTask::new,
Ned Burnsd8b51542020-03-13 20:52:43 -0400264 mExpandableNotificationRowComponentBuilder,
265 new IconManager(
266 mEntryManager,
267 mock(LauncherApps.class),
Kevin Hanf69b71e2020-04-13 15:46:14 -0700268 new IconBuilder(mContext)),
269 mock(LowPriorityInflationHelper.class));
Kevin Han225b7122020-01-31 15:42:42 -0800270
271 mEntryManager.setUpWithPresenter(mPresenter);
272 mEntryManager.addNotificationEntryListener(mEntryListener);
273
274 mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
Kevin Han225b7122020-01-31 15:42:42 -0800275 mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
276
277 Ranking ranking = new Ranking();
278 ranking.populate(
279 mSbn.getKey(),
280 0,
281 false,
282 0,
283 0,
284 IMPORTANCE_DEFAULT,
285 null,
286 null,
287 null,
288 null,
289 null,
290 true,
291 Ranking.USER_SENTIMENT_NEUTRAL,
292 false,
293 -1,
294 false,
295 null,
296 null,
297 false,
298 false,
Julia Reynolds138111f2020-02-26 11:17:39 -0500299 false,
Mady Mellor56515c42020-02-18 17:58:36 -0800300 null,
301 false);
Kevin Han225b7122020-01-31 15:42:42 -0800302 mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
303
304 TestableLooper.get(this).processAllMessages();
305 }
306
307 @After
308 public void cleanUp() {
309 // Don't leave anything on main thread
310 TestableLooper.get(this).processAllMessages();
311 }
312
313 @Test
314 public void testAddNotification() {
315 // WHEN a notification is added
316 mEntryManager.addNotification(mSbn, mRankingMap);
317 ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
318 NotificationEntry.class);
319 verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
320 NotificationEntry entry = entryCaptor.getValue();
321
Kevin Hana6d5bb42020-03-17 13:55:49 -0700322 waitForInflation();
Kevin Han225b7122020-01-31 15:42:42 -0800323
324 // THEN the notification has its row inflated
325 assertNotNull(entry.getRow());
326 assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
327
328 // THEN inflation callbacks are called
Kevin Han37423222020-03-10 16:05:07 -0700329 verify(mBindCallback).onBindRow(entry.getRow());
Kevin Han225b7122020-01-31 15:42:42 -0800330 verify(mEntryListener, never()).onInflationError(any(), any());
331 verify(mEntryListener).onEntryInflated(entry);
332 verify(mEntryListener).onNotificationAdded(entry);
333
334 // THEN the notification is active
335 assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
336
337 // THEN we update the presenter
338 verify(mPresenter).updateNotificationViews();
339 }
340
341 @Test
342 public void testUpdateNotification() {
343 // GIVEN a notification already added
344 mEntryManager.addNotification(mSbn, mRankingMap);
345 ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
346 NotificationEntry.class);
347 verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
348 NotificationEntry entry = entryCaptor.getValue();
Kevin Hana6d5bb42020-03-17 13:55:49 -0700349 waitForInflation();
Kevin Han225b7122020-01-31 15:42:42 -0800350
351 Mockito.reset(mEntryListener);
352 Mockito.reset(mPresenter);
353
354 // WHEN the notification is updated
355 mEntryManager.updateNotification(mSbn, mRankingMap);
356
Kevin Hana6d5bb42020-03-17 13:55:49 -0700357 waitForInflation();
Kevin Han225b7122020-01-31 15:42:42 -0800358
359 // THEN the notification has its row and inflated
360 assertNotNull(entry.getRow());
361
362 // THEN inflation callbacks are called
363 verify(mEntryListener, never()).onInflationError(any(), any());
364 verify(mEntryListener).onEntryReinflated(entry);
365
366 // THEN we update the presenter
367 verify(mPresenter).updateNotificationViews();
368 }
369
Kevin Han39ce15f2020-04-07 18:13:36 -0700370 @Test
371 public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
372 // GIVEN a notification is added and the row is inflating
373 mEntryManager.addNotification(mSbn, mRankingMap);
374 ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
375 NotificationEntry.class);
376 verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
377 NotificationEntry entry = entryCaptor.getValue();
378
379 // WHEN we try to bind a content view
380 mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
381 mRowContentBindStage.requestRebind(entry, null);
382
383 waitForInflation();
384
385 // THEN the notification has its row and all relevant content views inflated
386 assertNotNull(entry.getRow());
387 assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
388 assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
389 }
390
Kevin Hanc5711872020-03-04 22:23:02 +0000391 /**
Kevin Hana6d5bb42020-03-17 13:55:49 -0700392 * Wait for inflation to finish.
Kevin Hanc5711872020-03-04 22:23:02 +0000393 *
Kevin Hana6d5bb42020-03-17 13:55:49 -0700394 * A few things to note
395 * 1) Row inflation is done via {@link AsyncLayoutInflater} on its own background thread that
396 * calls back to main thread which is why we wait on main thread.
397 * 2) Row *content* inflation is done on the {@link FakeExecutor} we pass in in this test class
398 * so we control when that work is done. The callback is still always on the main thread.
Kevin Hanc5711872020-03-04 22:23:02 +0000399 */
Kevin Hana6d5bb42020-03-17 13:55:49 -0700400 private void waitForInflation() {
Kevin Han225b7122020-01-31 15:42:42 -0800401 mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
Kevin Hana6d5bb42020-03-17 13:55:49 -0700402 final CountDownLatch latch = new CountDownLatch(1);
403 NotificationEntryListener inflationListener = new NotificationEntryListener() {
404 @Override
405 public void onEntryInflated(NotificationEntry entry) {
406 latch.countDown();
407 }
408
409 @Override
410 public void onEntryReinflated(NotificationEntry entry) {
411 latch.countDown();
412 }
413
414 @Override
415 public void onInflationError(StatusBarNotification notification, Exception exception) {
416 latch.countDown();
417 }
418 };
419 mEntryManager.addNotificationEntryListener(inflationListener);
420 while (latch.getCount() != 0) {
421 mBgExecutor.runAllReady();
422 TestableLooper.get(this).processMessages(1);
423 }
Kevin Han225b7122020-01-31 15:42:42 -0800424 mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
Kevin Hana6d5bb42020-03-17 13:55:49 -0700425 mEntryManager.removeNotificationEntryListener(inflationListener);
Kevin Han225b7122020-01-31 15:42:42 -0800426 }
427
428}