blob: 7cfd7a3d18222b33f632dec2b258d78268e8638a [file] [log] [blame]
Eliot Courtneya6d8cf22017-10-20 13:26:58 +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.assertNotNull;
20import static junit.framework.Assert.assertNull;
21import static junit.framework.Assert.assertTrue;
22
23import static org.junit.Assert.assertEquals;
24import static org.mockito.ArgumentMatchers.any;
25import static org.mockito.ArgumentMatchers.anyInt;
Julia Reynoldsfc640012018-02-21 12:25:27 -050026import static org.mockito.ArgumentMatchers.anyString;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090027import static org.mockito.ArgumentMatchers.eq;
28import static org.mockito.Mockito.doAnswer;
29import static org.mockito.Mockito.mock;
30import static org.mockito.Mockito.never;
Julia Reynoldsfc640012018-02-21 12:25:27 -050031import static org.mockito.Mockito.times;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090032import static org.mockito.Mockito.verify;
33import static org.mockito.Mockito.when;
34
35import android.app.ActivityManager;
Julia Reynoldsfc640012018-02-21 12:25:27 -050036import android.app.AppOpsManager;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090037import android.app.Notification;
Dan Sandler1d958f82018-01-09 21:10:26 -050038import android.app.NotificationManager;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090039import android.content.Context;
40import android.os.Handler;
41import android.os.Looper;
42import android.os.UserHandle;
43import android.service.notification.NotificationListenerService;
44import android.service.notification.StatusBarNotification;
45import android.support.test.filters.SmallTest;
46import android.testing.AndroidTestingRunner;
47import android.testing.TestableLooper;
Julia Reynolds91590062018-04-02 16:24:11 -040048import android.util.ArraySet;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090049import android.widget.FrameLayout;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090050
51import com.android.internal.logging.MetricsLogger;
52import com.android.internal.statusbar.IStatusBarService;
53import com.android.systemui.ForegroundServiceController;
54import com.android.systemui.R;
55import com.android.systemui.SysuiTestCase;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090056import com.android.systemui.statusbar.notification.VisualStabilityManager;
57import com.android.systemui.statusbar.phone.NotificationGroupManager;
58import com.android.systemui.statusbar.policy.DeviceProvisionedController;
59import com.android.systemui.statusbar.policy.HeadsUpManager;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090060
Julia Reynolds91590062018-04-02 16:24:11 -040061import junit.framework.Assert;
62
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090063import org.junit.Before;
64import org.junit.Test;
65import org.junit.runner.RunWith;
66import org.mockito.ArgumentCaptor;
67import org.mockito.Mock;
68import org.mockito.MockitoAnnotations;
69
70import java.util.concurrent.CountDownLatch;
71import java.util.concurrent.TimeUnit;
72
73@SmallTest
74@RunWith(AndroidTestingRunner.class)
75@TestableLooper.RunWithLooper
76public class NotificationEntryManagerTest extends SysuiTestCase {
77 private static final String TEST_PACKAGE_NAME = "test";
78 private static final int TEST_UID = 0;
79
80 @Mock private NotificationPresenter mPresenter;
81 @Mock private ExpandableNotificationRow mRow;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090082 @Mock private NotificationListContainer mListContainer;
83 @Mock private NotificationEntryManager.Callback mCallback;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090084 @Mock private HeadsUpManager mHeadsUpManager;
85 @Mock private NotificationListenerService.RankingMap mRankingMap;
86 @Mock private RemoteInputController mRemoteInputController;
87 @Mock private IStatusBarService mBarService;
88
Eliot Courtney8f56b0e2017-12-14 18:54:28 +090089 // Dependency mocks:
90 @Mock private ForegroundServiceController mForegroundServiceController;
91 @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
92 @Mock private NotificationGroupManager mGroupManager;
93 @Mock private NotificationGutsManager mGutsManager;
94 @Mock private NotificationRemoteInputManager mRemoteInputManager;
95 @Mock private NotificationMediaManager mMediaManager;
96 @Mock private NotificationListener mNotificationListener;
97 @Mock private DeviceProvisionedController mDeviceProvisionedController;
98 @Mock private VisualStabilityManager mVisualStabilityManager;
99 @Mock private MetricsLogger mMetricsLogger;
100
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900101 private NotificationData.Entry mEntry;
102 private StatusBarNotification mSbn;
103 private Handler mHandler;
104 private TestableNotificationEntryManager mEntryManager;
105 private CountDownLatch mCountDownLatch;
106
107 private class TestableNotificationEntryManager extends NotificationEntryManager {
108 private final CountDownLatch mCountDownLatch;
109
Eliot Courtney6c313d32017-12-14 19:57:51 +0900110 public TestableNotificationEntryManager(Context context, IStatusBarService barService) {
111 super(context);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900112 mBarService = barService;
113 mCountDownLatch = new CountDownLatch(1);
114 mUseHeadsUp = true;
115 }
116
117 @Override
118 public void onAsyncInflationFinished(NotificationData.Entry entry) {
119 super.onAsyncInflationFinished(entry);
120
121 mCountDownLatch.countDown();
122 }
123
124 public CountDownLatch getCountDownLatch() {
125 return mCountDownLatch;
126 }
127 }
128
Dan Sandler1d958f82018-01-09 21:10:26 -0500129 private void setUserSentiment(String key, int sentiment) {
130 doAnswer(invocationOnMock -> {
131 NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
132 invocationOnMock.getArguments()[1];
133 ranking.populate(
134 key,
135 0,
136 false,
137 0,
138 0,
139 NotificationManager.IMPORTANCE_DEFAULT,
140 null, null,
Beverly5a20a5e2018-03-06 15:02:44 -0500141 null, null, null, true, sentiment, false);
Dan Sandler1d958f82018-01-09 21:10:26 -0500142 return true;
143 }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
144 }
145
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900146 @Before
147 public void setUp() {
148 MockitoAnnotations.initMocks(this);
Eliot Courtney8f56b0e2017-12-14 18:54:28 +0900149 mDependency.injectTestDependency(ForegroundServiceController.class,
150 mForegroundServiceController);
151 mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
152 mLockscreenUserManager);
153 mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
154 mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
155 mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
156 mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
157 mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
158 mDependency.injectTestDependency(DeviceProvisionedController.class,
159 mDeviceProvisionedController);
160 mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
161 mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
162
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900163 mHandler = new Handler(Looper.getMainLooper());
164 mCountDownLatch = new CountDownLatch(1);
165
166 when(mPresenter.getHandler()).thenReturn(mHandler);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900167 when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
168 when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
169 when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900170 when(mListContainer.getViewParentForNotification(any())).thenReturn(
171 new FrameLayout(mContext));
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900172
173 Notification.Builder n = new Notification.Builder(mContext, "")
174 .setSmallIcon(R.drawable.ic_person)
175 .setContentTitle("Title")
176 .setContentText("Text");
177 mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
178 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
179 mEntry = new NotificationData.Entry(mSbn);
180 mEntry.expandedIcon = mock(StatusBarIconView.class);
181
Eliot Courtney6c313d32017-12-14 19:57:51 +0900182 mEntryManager = new TestableNotificationEntryManager(mContext, mBarService);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900183 mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager);
Dan Sandler1d958f82018-01-09 21:10:26 -0500184
185 setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900186 }
187
188 @Test
189 public void testAddNotification() throws Exception {
190 com.android.systemui.util.Assert.isNotMainThread();
191
192 doAnswer(invocation -> {
193 mCountDownLatch.countDown();
194 return null;
195 }).when(mCallback).onBindRow(any(), any(), any(), any());
196
197 // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
198 // callback forever, since it won't execute until the tests ends.
199 mHandler.post(() -> {
200 mEntryManager.addNotification(mSbn, mRankingMap);
201 });
202 assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES));
203 assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES));
204 waitForIdleSync(mHandler);
205
206 // Check that no inflation error occurred.
207 verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
208 any(), anyInt());
209 verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
210
211 // Row inflation:
212 ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
213 NotificationData.Entry.class);
214 verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
215 NotificationData.Entry entry = entryCaptor.getValue();
216 verify(mRemoteInputManager).bindRow(entry.row);
217
218 // Row content inflation:
219 verify(mCallback).onNotificationAdded(entry);
220 verify(mPresenter).updateNotificationViews();
221
222 assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
223 assertNotNull(entry.row);
Dan Sandler1d958f82018-01-09 21:10:26 -0500224 assertEquals(mEntry.userSentiment,
225 NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900226 }
227
228 @Test
229 public void testUpdateNotification() throws Exception {
230 com.android.systemui.util.Assert.isNotMainThread();
231
232 mEntryManager.getNotificationData().add(mEntry);
233
Dan Sandler1d958f82018-01-09 21:10:26 -0500234 setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
235
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900236 mHandler.post(() -> {
237 mEntryManager.updateNotification(mSbn, mRankingMap);
238 });
239 // Wait for content update.
240 mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES);
241 waitForIdleSync(mHandler);
242
243 verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
244 any(), anyInt());
245
246 verify(mRemoteInputManager).onUpdateNotification(mEntry);
247 verify(mPresenter).updateNotificationViews();
248 verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
249 verify(mCallback).onNotificationUpdated(mSbn);
250 assertNotNull(mEntry.row);
Dan Sandler1d958f82018-01-09 21:10:26 -0500251 assertEquals(mEntry.userSentiment,
252 NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900253 }
254
255 @Test
256 public void testRemoveNotification() throws Exception {
257 com.android.systemui.util.Assert.isNotMainThread();
258
259 mEntry.row = mRow;
260 mEntryManager.getNotificationData().add(mEntry);
261
262 mHandler.post(() -> {
263 mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
264 });
265 waitForIdleSync(mHandler);
266
267 verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
268 any(), anyInt());
269
270 verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
271 verify(mRemoteInputManager).onRemoveNotification(mEntry);
272 verify(mForegroundServiceController).removeNotification(mSbn);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900273 verify(mListContainer).cleanUpViewState(mRow);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900274 verify(mPresenter).updateNotificationViews();
275 verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
276 verify(mRow).setRemoved();
277
278 assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
279 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500280
281 @Test
282 public void testUpdateAppOps_foregroundNoti() {
283 com.android.systemui.util.Assert.isNotMainThread();
284
285 when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString()))
Julia Reynolds91590062018-04-02 16:24:11 -0400286 .thenReturn(mEntry.key);
Julia Reynoldsfc640012018-02-21 12:25:27 -0500287 mEntry.row = mRow;
288 mEntryManager.getNotificationData().add(mEntry);
289
Julia Reynoldsfc640012018-02-21 12:25:27 -0500290 mHandler.post(() -> {
Julia Reynolds91590062018-04-02 16:24:11 -0400291 mEntryManager.updateNotificationsForAppOp(
Julia Reynoldsfc640012018-02-21 12:25:27 -0500292 AppOpsManager.OP_CAMERA, mEntry.notification.getUid(),
293 mEntry.notification.getPackageName(), true);
294 });
295 waitForIdleSync(mHandler);
296
297 verify(mPresenter, times(1)).updateNotificationViews();
298 assertTrue(mEntryManager.getNotificationData().get(mEntry.key).mActiveAppOps.contains(
299 AppOpsManager.OP_CAMERA));
300 }
301
302 @Test
303 public void testUpdateAppOps_otherNoti() {
304 com.android.systemui.util.Assert.isNotMainThread();
305
306 when(mForegroundServiceController.getStandardLayoutKey(anyInt(), anyString()))
307 .thenReturn(null);
308 mHandler.post(() -> {
Julia Reynolds91590062018-04-02 16:24:11 -0400309 mEntryManager.updateNotificationsForAppOp(AppOpsManager.OP_CAMERA, 1000, "pkg", true);
Julia Reynoldsfc640012018-02-21 12:25:27 -0500310 });
311 waitForIdleSync(mHandler);
312
313 verify(mPresenter, never()).updateNotificationViews();
314 }
Julia Reynolds91590062018-04-02 16:24:11 -0400315
316 @Test
317 public void testAddNotificationExistingAppOps() {
318 mEntry.row = mRow;
319 mEntryManager.getNotificationData().add(mEntry);
320 ArraySet<Integer> expected = new ArraySet<>();
321 expected.add(3);
322 expected.add(235);
323 expected.add(1);
324
325 when(mForegroundServiceController.getAppOps(mEntry.notification.getUserId(),
326 mEntry.notification.getPackageName())).thenReturn(expected);
327 when(mForegroundServiceController.getStandardLayoutKey(
328 mEntry.notification.getUserId(),
329 mEntry.notification.getPackageName())).thenReturn(mEntry.key);
330
331 mEntryManager.tagForeground(mEntry.notification);
332
333 Assert.assertEquals(expected.size(), mEntry.mActiveAppOps.size());
334 for (int op : expected) {
335 assertTrue("Entry missing op " + op, mEntry.mActiveAppOps.contains(op));
336 }
337 }
338
339 @Test
340 public void testAdd_noExistingAppOps() {
341 mEntry.row = mRow;
342 mEntryManager.getNotificationData().add(mEntry);
343 when(mForegroundServiceController.getStandardLayoutKey(
344 mEntry.notification.getUserId(),
345 mEntry.notification.getPackageName())).thenReturn(mEntry.key);
346 when(mForegroundServiceController.getAppOps(mEntry.notification.getUserId(),
347 mEntry.notification.getPackageName())).thenReturn(null);
348
349 mEntryManager.tagForeground(mEntry.notification);
350 Assert.assertEquals(0, mEntry.mActiveAppOps.size());
351 }
352
353 @Test
354 public void testAdd_existingAppOpsNotForegroundNoti() {
355 mEntry.row = mRow;
356 mEntryManager.getNotificationData().add(mEntry);
357 ArraySet<Integer> ops = new ArraySet<>();
358 ops.add(3);
359 ops.add(235);
360 ops.add(1);
361 when(mForegroundServiceController.getAppOps(mEntry.notification.getUserId(),
362 mEntry.notification.getPackageName())).thenReturn(ops);
363 when(mForegroundServiceController.getStandardLayoutKey(
364 mEntry.notification.getUserId(),
365 mEntry.notification.getPackageName())).thenReturn("something else");
366
367 mEntryManager.tagForeground(mEntry.notification);
368 Assert.assertEquals(0, mEntry.mActiveAppOps.size());
369 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900370}