blob: 0248dc565aa128dd3ec4c913be6afd7c763a8b03 [file] [log] [blame]
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001/*
2 * Copyright (C) 2017 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;
18
19import static junit.framework.Assert.assertTrue;
20
21import static org.junit.Assert.assertEquals;
Julia Reynoldsfc640012018-02-21 12:25:27 -050022import static org.mockito.ArgumentMatchers.any;
Ned Burnsb3b4bd32019-06-27 19:36:58 -040023import static org.mockito.ArgumentMatchers.anyInt;
24import static org.mockito.Mockito.clearInvocations;
25import static org.mockito.Mockito.doAnswer;
Jason Monk09f4d372018-12-20 15:30:54 -050026import static org.mockito.Mockito.mock;
Julia Reynoldsfc640012018-02-21 12:25:27 -050027import static org.mockito.Mockito.spy;
28import static org.mockito.Mockito.times;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090029import static org.mockito.Mockito.verify;
30import static org.mockito.Mockito.when;
31
Ned Burnsb3b4bd32019-06-27 19:36:58 -040032import android.os.Handler;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090033import android.testing.AndroidTestingRunner;
34import android.testing.TestableLooper;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.LinearLayout;
38
Brett Chabot84151d92019-02-27 15:37:59 -080039import androidx.test.filters.SmallTest;
40
Jason Monk297c04e2018-08-23 17:16:59 -040041import com.android.systemui.Dependency;
42import com.android.systemui.InitController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090043import com.android.systemui.SysuiTestCase;
Mady Mellorce23c462019-06-17 17:30:07 -070044import com.android.systemui.bubbles.BubbleController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090045import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
Selim Cinek6f0a62a2019-04-09 18:40:12 -070046import com.android.systemui.statusbar.notification.DynamicPrivacyController;
Rohan Shah20790b82018-07-02 17:21:04 -070047import com.android.systemui.statusbar.notification.NotificationEntryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090048import com.android.systemui.statusbar.notification.VisualStabilityManager;
Ned Burnsf81c4c42019-01-07 14:10:43 -050049import com.android.systemui.statusbar.notification.collection.NotificationData;
50import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Rohan Shah20790b82018-07-02 17:21:04 -070051import com.android.systemui.statusbar.notification.logging.NotificationLogger;
52import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
53import com.android.systemui.statusbar.notification.row.ExpandableView;
54import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
Selim Cinekb0fada62019-06-17 19:03:59 -070055import com.android.systemui.statusbar.phone.KeyguardBypassController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090056import com.android.systemui.statusbar.phone.NotificationGroupManager;
Jason Monk297c04e2018-08-23 17:16:59 -040057import com.android.systemui.statusbar.phone.ShadeController;
Jason Monka716bac2018-12-05 15:48:21 -050058import com.android.systemui.util.Assert;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090059
60import com.google.android.collect.Lists;
61
62import org.junit.Before;
63import org.junit.Test;
64import org.junit.runner.RunWith;
65import org.mockito.Mock;
66import org.mockito.MockitoAnnotations;
67import org.mockito.Spy;
68
69import java.util.List;
70
71@SmallTest
72@RunWith(AndroidTestingRunner.class)
Jason Monka716bac2018-12-05 15:48:21 -050073@TestableLooper.RunWithLooper
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090074public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
75 @Mock private NotificationPresenter mPresenter;
Eliot Courtney8f56b0e2017-12-14 18:54:28 +090076 @Mock private NotificationData mNotificationData;
77 @Spy private FakeListContainer mListContainer = new FakeListContainer();
78
79 // Dependency mocks:
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090080 @Mock private NotificationEntryManager mEntryManager;
81 @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
82 @Mock private NotificationGroupManager mGroupManager;
83 @Mock private VisualStabilityManager mVisualStabilityManager;
Jason Monk297c04e2018-08-23 17:16:59 -040084 @Mock private ShadeController mShadeController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090085
Ned Burnsb3b4bd32019-06-27 19:36:58 -040086 private TestableLooper mTestableLooper;
87 private Handler mHandler;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090088 private NotificationViewHierarchyManager mViewHierarchyManager;
Ned Burns01e38212019-01-03 16:32:52 -050089 private NotificationTestHelper mHelper;
Ned Burnsb3b4bd32019-06-27 19:36:58 -040090 private boolean mMadeReentrantCall = false;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090091
92 @Before
93 public void setUp() {
94 MockitoAnnotations.initMocks(this);
Ned Burnsb3b4bd32019-06-27 19:36:58 -040095 mTestableLooper = TestableLooper.get(this);
96 Assert.sMainLooper = mTestableLooper.getLooper();
97 mHandler = Handler.createAsync(mTestableLooper.getLooper());
98
Eliot Courtney8f56b0e2017-12-14 18:54:28 +090099 mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
100 mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
101 mLockscreenUserManager);
102 mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
103 mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
Jason Monk297c04e2018-08-23 17:16:59 -0400104 mDependency.injectTestDependency(ShadeController.class, mShadeController);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900105
Ned Burns01e38212019-01-03 16:32:52 -0500106 mHelper = new NotificationTestHelper(mContext);
107
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900108 when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
109
Jason Monk09f4d372018-12-20 15:30:54 -0500110 mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
Ned Burnsb3b4bd32019-06-27 19:36:58 -0400111 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
Beverly8fdb5332019-02-04 14:29:49 -0500112 mock(StatusBarStateControllerImpl.class), mEntryManager,
Mady Mellorce23c462019-06-17 17:30:07 -0700113 () -> mShadeController,
Selim Cinekb0fada62019-06-17 19:03:59 -0700114 mock(KeyguardBypassController.class),
Mady Mellorce23c462019-06-17 17:30:07 -0700115 mock(BubbleController.class),
Selim Cinekb0fada62019-06-17 19:03:59 -0700116 mock(DynamicPrivacyController.class));
Jason Monk297c04e2018-08-23 17:16:59 -0400117 Dependency.get(InitController.class).executePostInitTasks();
118 mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900119 }
120
Ned Burnsf81c4c42019-01-07 14:10:43 -0500121 private NotificationEntry createEntry() throws Exception {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900122 ExpandableNotificationRow row = mHelper.createRow();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500123 NotificationEntry entry = new NotificationEntry(row.getStatusBarNotification());
Evan Laird94492852018-10-25 13:43:01 -0400124 entry.setRow(row);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900125 return entry;
126 }
127
128 @Test
129 public void testNotificationsBecomingBundled() throws Exception {
130 // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
131 // the summary.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500132 NotificationEntry entry0 = createEntry();
133 NotificationEntry entry1 = createEntry();
134 NotificationEntry entry2 = createEntry();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900135
136 // Set up the prior state to look like three top level notifications.
Evan Laird94492852018-10-25 13:43:01 -0400137 mListContainer.addContainerView(entry0.getRow());
138 mListContainer.addContainerView(entry1.getRow());
139 mListContainer.addContainerView(entry2.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900140 when(mNotificationData.getActiveNotifications()).thenReturn(
141 Lists.newArrayList(entry0, entry1, entry2));
142
143 // Set up group manager to report that they should be bundled now.
144 when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
145 when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(true);
146 when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(true);
Evan Laird94492852018-10-25 13:43:01 -0400147 when(mGroupManager.getGroupSummary(entry1.notification)).thenReturn(entry0);
148 when(mGroupManager.getGroupSummary(entry2.notification)).thenReturn(entry0);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900149
150 // Run updateNotifications - the view hierarchy should be reorganized.
151 mViewHierarchyManager.updateNotificationViews();
152
Evan Laird94492852018-10-25 13:43:01 -0400153 verify(mListContainer).notifyGroupChildAdded(entry1.getRow());
154 verify(mListContainer).notifyGroupChildAdded(entry2.getRow());
155 assertTrue(Lists.newArrayList(entry0.getRow()).equals(mListContainer.mRows));
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900156 }
157
158 @Test
159 public void testNotificationsBecomingUnbundled() throws Exception {
160 // Tests a bundled notification becoming three top level notifications.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500161 NotificationEntry entry0 = createEntry();
162 NotificationEntry entry1 = createEntry();
163 NotificationEntry entry2 = createEntry();
Evan Laird94492852018-10-25 13:43:01 -0400164 entry0.getRow().addChildNotification(entry1.getRow());
165 entry0.getRow().addChildNotification(entry2.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900166
167 // Set up the prior state to look like one top level notification.
Evan Laird94492852018-10-25 13:43:01 -0400168 mListContainer.addContainerView(entry0.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900169 when(mNotificationData.getActiveNotifications()).thenReturn(
170 Lists.newArrayList(entry0, entry1, entry2));
171
172 // Set up group manager to report that they should not be bundled now.
173 when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
174 when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
175 when(mGroupManager.isChildInGroupWithSummary(entry2.notification)).thenReturn(false);
176
177 // Run updateNotifications - the view hierarchy should be reorganized.
178 mViewHierarchyManager.updateNotificationViews();
179
180 verify(mListContainer).notifyGroupChildRemoved(
Evan Laird94492852018-10-25 13:43:01 -0400181 entry1.getRow(), entry0.getRow().getChildrenContainer());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900182 verify(mListContainer).notifyGroupChildRemoved(
Evan Laird94492852018-10-25 13:43:01 -0400183 entry2.getRow(), entry0.getRow().getChildrenContainer());
184 assertTrue(
185 Lists.newArrayList(entry0.getRow(), entry1.getRow(), entry2.getRow())
186 .equals(mListContainer.mRows));
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900187 }
188
189 @Test
190 public void testNotificationsBecomingSuppressed() throws Exception {
191 // Tests two top level notifications becoming a suppressed summary and a child.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500192 NotificationEntry entry0 = createEntry();
193 NotificationEntry entry1 = createEntry();
Evan Laird94492852018-10-25 13:43:01 -0400194 entry0.getRow().addChildNotification(entry1.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900195
196 // Set up the prior state to look like a top level notification.
Evan Laird94492852018-10-25 13:43:01 -0400197 mListContainer.addContainerView(entry0.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900198 when(mNotificationData.getActiveNotifications()).thenReturn(
199 Lists.newArrayList(entry0, entry1));
200
201 // Set up group manager to report a suppressed summary now.
202 when(mGroupManager.isChildInGroupWithSummary(entry0.notification)).thenReturn(false);
203 when(mGroupManager.isChildInGroupWithSummary(entry1.notification)).thenReturn(false);
204 when(mGroupManager.isSummaryOfSuppressedGroup(entry0.notification)).thenReturn(true);
205
206 // Run updateNotifications - the view hierarchy should be reorganized.
207 mViewHierarchyManager.updateNotificationViews();
208
209 verify(mListContainer).notifyGroupChildRemoved(
Evan Laird94492852018-10-25 13:43:01 -0400210 entry1.getRow(), entry0.getRow().getChildrenContainer());
211 assertTrue(Lists.newArrayList(entry0.getRow(), entry1.getRow()).equals(mListContainer.mRows));
212 assertEquals(View.GONE, entry0.getRow().getVisibility());
213 assertEquals(View.VISIBLE, entry1.getRow().getVisibility());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900214 }
215
Julia Reynoldsfc640012018-02-21 12:25:27 -0500216 @Test
217 public void testUpdateNotificationViews_appOps() throws Exception {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500218 NotificationEntry entry0 = createEntry();
Evan Laird94492852018-10-25 13:43:01 -0400219 entry0.setRow(spy(entry0.getRow()));
Julia Reynoldsfc640012018-02-21 12:25:27 -0500220 when(mNotificationData.getActiveNotifications()).thenReturn(
221 Lists.newArrayList(entry0));
Evan Laird94492852018-10-25 13:43:01 -0400222 mListContainer.addContainerView(entry0.getRow());
Julia Reynoldsfc640012018-02-21 12:25:27 -0500223
224 mViewHierarchyManager.updateNotificationViews();
225
Evan Laird94492852018-10-25 13:43:01 -0400226 verify(entry0.getRow(), times(1)).showAppOpsIcons(any());
Julia Reynoldsfc640012018-02-21 12:25:27 -0500227 }
228
Ned Burnsb3b4bd32019-06-27 19:36:58 -0400229 @Test
230 public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
231 // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
232 mMadeReentrantCall = false;
233 doAnswer((invocation) -> {
234 if (!mMadeReentrantCall) {
235 mMadeReentrantCall = true;
236 mViewHierarchyManager.onDynamicPrivacyChanged();
237 }
238 return null;
239 }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
240
241 // WHEN we call updateNotificationViews()
242 mViewHierarchyManager.updateNotificationViews();
243
244 // THEN onNotificationViewUpdateFinished() is only called once
245 verify(mListContainer).onNotificationViewUpdateFinished();
246
247 // WHEN we drain the looper
248 mTestableLooper.processAllMessages();
249
250 // THEN updateNotificationViews() is called a second time (for the reentrant call)
251 verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
252 }
253
254 @Test
255 public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
256 // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
257 mMadeReentrantCall = false;
258 doAnswer((invocation) -> {
259 if (!mMadeReentrantCall) {
260 mMadeReentrantCall = true;
261 mViewHierarchyManager.onDynamicPrivacyChanged();
262 mViewHierarchyManager.onDynamicPrivacyChanged();
263 mViewHierarchyManager.onDynamicPrivacyChanged();
264 mViewHierarchyManager.onDynamicPrivacyChanged();
265 }
266 return null;
267 }).when(mListContainer).setMaxDisplayedNotifications(anyInt());
268
269 // WHEN we call updateNotificationViews() and drain the looper
270 mViewHierarchyManager.updateNotificationViews();
271 verify(mListContainer).onNotificationViewUpdateFinished();
272 clearInvocations(mListContainer);
273 mTestableLooper.processAllMessages();
274
275 // THEN updateNotificationViews() is called only one more time
276 verify(mListContainer).onNotificationViewUpdateFinished();
277 }
278
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900279 private class FakeListContainer implements NotificationListContainer {
280 final LinearLayout mLayout = new LinearLayout(mContext);
281 final List<View> mRows = Lists.newArrayList();
Ned Burnsb3b4bd32019-06-27 19:36:58 -0400282 private boolean mMakeReentrantCallDuringSetMaxDisplayedNotifications;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900283
284 @Override
285 public void setChildTransferInProgress(boolean childTransferInProgress) {}
286
287 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500288 public void changeViewPosition(ExpandableView child, int newIndex) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900289 mRows.remove(child);
290 mRows.add(newIndex, child);
291 }
292
293 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500294 public void notifyGroupChildAdded(ExpandableView row) {}
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900295
296 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500297 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {}
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900298
299 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500300 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {}
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900301
302 @Override
303 public void generateChildOrderChangedEvent() {}
304
305 @Override
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400306 public void onReset(ExpandableView view) {}
307
308 @Override
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900309 public int getContainerChildCount() {
310 return mRows.size();
311 }
312
313 @Override
314 public View getContainerChildAt(int i) {
315 return mRows.get(i);
316 }
317
318 @Override
319 public void removeContainerView(View v) {
320 mLayout.removeView(v);
321 mRows.remove(v);
322 }
323
324 @Override
325 public void addContainerView(View v) {
326 mLayout.addView(v);
327 mRows.add(v);
328 }
329
330 @Override
Ned Burnsb3b4bd32019-06-27 19:36:58 -0400331 public void setMaxDisplayedNotifications(int maxNotifications) {
332 if (mMakeReentrantCallDuringSetMaxDisplayedNotifications) {
333 mViewHierarchyManager.onDynamicPrivacyChanged();
334 }
335 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900336
337 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500338 public ViewGroup getViewParentForNotification(NotificationEntry entry) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900339 return null;
340 }
341
342 @Override
343 public void onHeightChanged(ExpandableView view, boolean animate) {}
344
345 @Override
346 public void resetExposedMenuView(boolean animate, boolean force) {}
347
348 @Override
349 public NotificationSwipeActionHelper getSwipeActionHelper() {
350 return null;
351 }
352
353 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500354 public void cleanUpViewStateForEntry(NotificationEntry entry) { }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900355
356 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500357 public boolean isInVisibleLocation(NotificationEntry entry) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900358 return true;
359 }
360
361 @Override
362 public void setChildLocationsChangedListener(
363 NotificationLogger.OnChildLocationsChangedListener listener) {}
364
365 @Override
366 public boolean hasPulsingNotifications() {
367 return false;
368 }
Evan Laird94492852018-10-25 13:43:01 -0400369
Ned Burnsb3b4bd32019-06-27 19:36:58 -0400370 @Override
371 public void onNotificationViewUpdateFinished() { }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900372 }
373}