1 /*
2  * Copyright (C) 2022 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 
17 package com.android.car.notification;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyLong;
24 import static org.mockito.ArgumentMatchers.anyString;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.ArgumentMatchers.nullable;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import android.app.ActivityManager;
34 import android.app.ActivityTaskManager;
35 import android.app.Notification;
36 import android.app.NotificationManager;
37 import android.app.PendingIntent;
38 import android.app.TaskStackListener;
39 import android.content.ComponentName;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.service.notification.NotificationListenerService;
43 import android.service.notification.StatusBarNotification;
44 import android.testing.TestableContext;
45 
46 import androidx.test.ext.junit.runners.AndroidJUnit4;
47 import androidx.test.platform.app.InstrumentationRegistry;
48 
49 import com.android.dx.mockito.inline.extended.ExtendedMockito;
50 
51 import org.junit.After;
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Captor;
58 import org.mockito.Mock;
59 import org.mockito.MockitoSession;
60 import org.mockito.quality.Strictness;
61 
62 import java.time.Clock;
63 import java.time.Instant;
64 import java.time.ZoneId;
65 import java.util.ArrayList;
66 import java.util.Collections;
67 import java.util.PriorityQueue;
68 import java.util.concurrent.ScheduledExecutorService;
69 import java.util.concurrent.ScheduledFuture;
70 import java.util.concurrent.TimeUnit;
71 
72 @RunWith(AndroidJUnit4.class)
73 public class CarHeadsUpNotificationQueueTest {
74     private static final int USER_ID = ActivityManager.getCurrentUser();
75 
76     private MockitoSession mSession;
77     private CarHeadsUpNotificationQueue mCarHeadsUpNotificationQueue;
78 
79     @Mock
80     private CarHeadsUpNotificationQueue.CarHeadsUpNotificationQueueCallback
81             mCarHeadsUpNotificationQueueCallback;
82     @Mock
83     private NotificationListenerService.RankingMap mRankingMap;
84     @Mock
85     private ActivityTaskManager mActivityTaskManager;
86     @Mock
87     private ScheduledExecutorService mScheduledExecutorService;
88     @Mock
89     private NotificationManager mNotificationManager;
90 
91     @Captor
92     private ArgumentCaptor<TaskStackListener> mTaskStackListenerArg;
93     @Captor
94     private ArgumentCaptor<AlertEntry> mAlertEntryArg;
95     @Captor
96     private ArgumentCaptor<Notification> mNotificationArg;
97 
98     @Rule
99     public final TestableContext mContext = new TestableContext(
100             InstrumentationRegistry.getInstrumentation().getTargetContext());
101 
102     private static final String PKG_1 = "PKG_1";
103     private static final String PKG_2 = "PKG_2";
104     private static final String PKG_3 = "PKG_3";
105     private static final String CHANNEL_ID = "CHANNEL_ID";
106 
107     @Before
setup()108     public void setup() {
109         mSession = ExtendedMockito.mockitoSession()
110                 .initMocks(this)
111                 .spyStatic(NotificationUtils.class)
112                 .strictness(Strictness.LENIENT)
113                 .startMocking();
114         // To add elements to the queue rather than displaying immediately
115         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
116                 new ArrayList<>(Collections.singletonList(mock(AlertEntry.class))));
117         ExtendedMockito.doReturn(USER_ID).when(() -> NotificationUtils.getCurrentUser(any()));
118     }
119 
120     @After
tearDown()121     public void tearDown() {
122         if (mSession != null) {
123             mSession.finishMocking();
124             mSession = null;
125         }
126     }
127 
createCarHeadsUpNotificationQueue()128     private CarHeadsUpNotificationQueue createCarHeadsUpNotificationQueue() {
129         return new CarHeadsUpNotificationQueue(mContext, mActivityTaskManager, mNotificationManager,
130                 mScheduledExecutorService, mCarHeadsUpNotificationQueueCallback);
131     }
132 
133     @Test
addToQueue_prioritises_postTimeOfHeadsUp()134     public void addToQueue_prioritises_postTimeOfHeadsUp() {
135         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
136         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
137                 "key1", "msg"), 4000);
138         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
139                 "key2", "msg"), 2000);
140         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
141                 "key3", "msg"), 3000);
142         AlertEntry alertEntry4 = new AlertEntry(generateMockStatusBarNotification(
143                 "key4", "msg"), 5000);
144         AlertEntry alertEntry5 = new AlertEntry(generateMockStatusBarNotification(
145                 "key5", "msg"), 1000);
146 
147         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
148         mCarHeadsUpNotificationQueue.addToQueue(alertEntry2, mRankingMap);
149         mCarHeadsUpNotificationQueue.addToQueue(alertEntry3, mRankingMap);
150         mCarHeadsUpNotificationQueue.addToQueue(alertEntry4, mRankingMap);
151         mCarHeadsUpNotificationQueue.addToQueue(alertEntry5, mRankingMap);
152 
153         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
154         assertThat(result.size()).isEqualTo(5);
155         assertThat(result.poll()).isEqualTo("key5");
156         assertThat(result.poll()).isEqualTo("key2");
157         assertThat(result.poll()).isEqualTo("key3");
158         assertThat(result.poll()).isEqualTo("key1");
159         assertThat(result.poll()).isEqualTo("key4");
160     }
161 
162     @Test
addToQueue_prioritises_categoriesOfHeadsUp()163     public void addToQueue_prioritises_categoriesOfHeadsUp() {
164         mContext.getOrCreateTestableResources().addOverride(
165                 R.array.headsup_category_immediate_show, /* value= */ new String[0]);
166         mContext.getOrCreateTestableResources().addOverride(
167                 R.array.headsup_category_priority, /* value= */ new String[]{
168                         "car_emergency", "navigation", "call", "msg"});
169         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
170         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
171                 "key1", "navigation"), 1000);
172         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
173                 "key2", "car_emergency"), 1000);
174         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
175                 "key3", "msg"), 1000);
176         AlertEntry alertEntry4 = new AlertEntry(generateMockStatusBarNotification(
177                 "key4", "call"), 1000);
178         AlertEntry alertEntry5 = new AlertEntry(generateMockStatusBarNotification(
179                 "key5", "msg"), 1000);
180 
181         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
182         mCarHeadsUpNotificationQueue.addToQueue(alertEntry2, mRankingMap);
183         mCarHeadsUpNotificationQueue.addToQueue(alertEntry3, mRankingMap);
184         mCarHeadsUpNotificationQueue.addToQueue(alertEntry4, mRankingMap);
185         mCarHeadsUpNotificationQueue.addToQueue(alertEntry5, mRankingMap);
186 
187         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
188         assertThat(result.size()).isEqualTo(5);
189         assertThat(result.poll()).isEqualTo("key2");
190         assertThat(result.poll()).isEqualTo("key1");
191         assertThat(result.poll()).isEqualTo("key4");
192         assertThat(result.poll()).isEqualTo("key3");
193         assertThat(result.poll()).isEqualTo("key5");
194     }
195 
196     @Test
addToQueue_prioritises_internalCategoryAsLeastPriority()197     public void addToQueue_prioritises_internalCategoryAsLeastPriority() {
198         mContext.getOrCreateTestableResources().addOverride(
199                 R.array.headsup_category_immediate_show, /* value= */ new String[0]);
200         mContext.getOrCreateTestableResources().addOverride(
201                 R.array.headsup_category_priority, /* value= */ new String[]{"msg"});
202         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
203         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
204                 "key1", "msg"), 1000);
205         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
206                 "key2", "HUN_QUEUE_INTERNAL"), 1000);
207         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
208                 "key3", "msg"), 1000);
209         AlertEntry alertEntry4 = new AlertEntry(generateMockStatusBarNotification(
210                 "key4", "msg"), 1000);
211 
212         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
213         mCarHeadsUpNotificationQueue.addToQueue(alertEntry2, mRankingMap);
214         mCarHeadsUpNotificationQueue.addToQueue(alertEntry3, mRankingMap);
215         mCarHeadsUpNotificationQueue.addToQueue(alertEntry4, mRankingMap);
216 
217         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
218         assertThat(result.size()).isEqualTo(4);
219         assertThat(result.poll()).isNotEqualTo("key2");
220         assertThat(result.poll()).isNotEqualTo("key2");
221         assertThat(result.poll()).isNotEqualTo("key2");
222         assertThat(result.poll()).isEqualTo("key2");
223     }
224 
225     @Test
addToQueue_merges_newHeadsUpWithSameKey()226     public void addToQueue_merges_newHeadsUpWithSameKey() {
227         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
228         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
229                 "key1", "msg"), 1000);
230         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
231                 "key2", "msg"), 2000);
232         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
233                 "key1", "msg"), 3000);
234 
235         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
236         mCarHeadsUpNotificationQueue.addToQueue(alertEntry2, mRankingMap);
237         mCarHeadsUpNotificationQueue.addToQueue(alertEntry3, mRankingMap);
238 
239         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
240         assertThat(result.size()).isEqualTo(2);
241         assertThat(result.poll()).isEqualTo("key1");
242         assertThat(result.poll()).isEqualTo("key2");
243     }
244 
245     @Test
addToQueue_shows_immediateShowHeadsUp()246     public void addToQueue_shows_immediateShowHeadsUp() {
247         mContext.getOrCreateTestableResources().addOverride(
248                 R.array.headsup_category_immediate_show, /* value= */
249                 new String[]{"car_emergency"});
250         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
251         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
252                 "key1", "msg"), 1000);
253         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
254                 "key2", "car_emergency"), 2000);
255         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
256                 "key3", "msg"), 3000);
257         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
258                 new ArrayList<>(Collections.singletonList(alertEntry3)));
259 
260         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
261         mCarHeadsUpNotificationQueue.addToQueue(alertEntry2, mRankingMap);
262 
263         verify(mCarHeadsUpNotificationQueueCallback).dismissHeadsUp(alertEntry3);
264         verify(mCarHeadsUpNotificationQueueCallback)
265                 .showAsHeadsUp(mAlertEntryArg.capture(),
266                         any(NotificationListenerService.RankingMap.class));
267         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key2");
268     }
269 
270     @Test
addToQueue_handles_notificationWithNoCategory()271     public void addToQueue_handles_notificationWithNoCategory() {
272         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
273         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
274                 "key1", /* category= */ null), 4000);
275 
276         mCarHeadsUpNotificationQueue.addToQueue(alertEntry1, mRankingMap);
277 
278         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
279         assertThat(result.size()).isEqualTo(1);
280         assertThat(result.poll()).isEqualTo("key1");
281     }
282 
283     @Test
triggerCallback_expireNotifications_whenParked()284     public void triggerCallback_expireNotifications_whenParked() {
285         mContext.getOrCreateTestableResources().addOverride(
286                 R.bool.config_expireHeadsUpWhenParked, /* value= */ true);
287         mContext.getOrCreateTestableResources().addOverride(
288                 R.integer.headsup_queue_expire_parked_duration_ms, /* value= */ 1000);
289         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
290         mCarHeadsUpNotificationQueue.setActiveUxRestriction(false); // car is parked
291         Instant now = Instant.now();
292         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
293         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
294                 "key1", "msg"), now.minusMillis(3000).toEpochMilli());
295         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
296                 "key2", "msg"), now.minusMillis(500).toEpochMilli());
297         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
298                 "key3", "msg"), now.toEpochMilli());
299         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
300         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
301         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry3);
302         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
303                 new ArrayList<>());
304 
305         mCarHeadsUpNotificationQueue.triggerCallback();
306 
307         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
308         verify(mCarHeadsUpNotificationQueueCallback)
309                 .removedFromHeadsUpQueue(mAlertEntryArg.capture());
310         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
311         verify(mCarHeadsUpNotificationQueueCallback)
312                 .showAsHeadsUp(mAlertEntryArg.capture(),
313                         nullable(NotificationListenerService.RankingMap.class));
314         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key2");
315         assertThat(result.contains("key3")).isTrue();
316     }
317 
318     @Test
triggerCallback_doesNot_expireNotifications_whenParked()319     public void triggerCallback_doesNot_expireNotifications_whenParked() {
320         mContext.getOrCreateTestableResources().addOverride(
321                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
322         mContext.getOrCreateTestableResources().addOverride(
323                 R.integer.headsup_queue_expire_driving_duration_ms, /* value= */ 1000);
324         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
325         mCarHeadsUpNotificationQueue.setActiveUxRestriction(false); // car is parked
326         Instant now = Instant.now();
327         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
328         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
329                 "key1", "msg"), now.minusMillis(3000).toEpochMilli());
330         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
331                 "key2", "msg"), now.minusMillis(500).toEpochMilli());
332         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
333                 "key3", "msg"), now.toEpochMilli());
334         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
335         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
336         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry3);
337         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
338                 new ArrayList<>());
339 
340         mCarHeadsUpNotificationQueue.triggerCallback();
341 
342         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
343         verify(mCarHeadsUpNotificationQueueCallback)
344                 .showAsHeadsUp(mAlertEntryArg.capture(),
345                         nullable(NotificationListenerService.RankingMap.class));
346         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
347         assertThat(result.contains("key2")).isTrue();
348         assertThat(result.contains("key3")).isTrue();
349     }
350 
351     @Test
triggerCallback_expireNotifications_whenDriving()352     public void triggerCallback_expireNotifications_whenDriving() {
353         mContext.getOrCreateTestableResources().addOverride(
354                 R.bool.config_expireHeadsUpWhenDriving, /* value= */ true);
355         mContext.getOrCreateTestableResources().addOverride(
356                 R.integer.headsup_queue_expire_driving_duration_ms, /* value= */ 1000);
357         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
358         mCarHeadsUpNotificationQueue.setActiveUxRestriction(true); // car is driving
359         Instant now = Instant.now();
360         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
361         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
362                 "key1", "msg"), now.minusMillis(3000).toEpochMilli());
363         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
364                 "key2", "msg"), now.minusMillis(500).toEpochMilli());
365         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
366                 "key3", "msg"), now.toEpochMilli());
367         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
368         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
369         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry3);
370         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
371                 new ArrayList<>());
372 
373         mCarHeadsUpNotificationQueue.triggerCallback();
374 
375         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
376         verify(mCarHeadsUpNotificationQueueCallback)
377                 .removedFromHeadsUpQueue(mAlertEntryArg.capture());
378         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
379         verify(mCarHeadsUpNotificationQueueCallback)
380                 .showAsHeadsUp(mAlertEntryArg.capture(),
381                         nullable(NotificationListenerService.RankingMap.class));
382         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key2");
383         assertThat(result.contains("key3")).isTrue();
384     }
385 
386     @Test
triggerCallback_doesNot_expireNotifications_forInternalCategory()387     public void triggerCallback_doesNot_expireNotifications_forInternalCategory() {
388         mContext.getOrCreateTestableResources().addOverride(
389                 R.bool.config_expireHeadsUpWhenParked, /* value= */ true);
390         mContext.getOrCreateTestableResources().addOverride(
391                 R.integer.headsup_queue_expire_parked_duration_ms, /* value= */ 1000);
392         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
393         mCarHeadsUpNotificationQueue.setActiveUxRestriction(false); // car is parked
394         Instant now = Instant.now();
395         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
396         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
397                 "key1", "HUN_QUEUE_INTERNAL"), now.minusMillis(3000).toEpochMilli());
398         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
399         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
400                 new ArrayList<>());
401 
402         mCarHeadsUpNotificationQueue.triggerCallback();
403 
404         verify(mCarHeadsUpNotificationQueueCallback, times(0))
405                 .removedFromHeadsUpQueue(mAlertEntryArg.capture());
406     }
407 
408     @Test
triggerCallback_setHunExpiredFlagToTrue_onHunExpired()409     public void triggerCallback_setHunExpiredFlagToTrue_onHunExpired() {
410         mContext.getOrCreateTestableResources().addOverride(
411                 R.bool.config_expireHeadsUpWhenParked, /* value= */ true);
412         mContext.getOrCreateTestableResources().addOverride(
413                 R.integer.headsup_queue_expire_parked_duration_ms, /* value= */ 1000);
414         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
415         mCarHeadsUpNotificationQueue.setActiveUxRestriction(false); // car is parked
416         Instant now = Instant.now();
417         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
418         AlertEntry alertEntry_expired = new AlertEntry(generateMockStatusBarNotification(
419                 "key1", "msg"), now.minusMillis(3000).toEpochMilli());
420         AlertEntry alertEntry_notExpired = new AlertEntry(generateMockStatusBarNotification(
421                 "key2", "msg"), now.minusMillis(500).toEpochMilli());
422         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry_expired);
423         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry_notExpired);
424         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
425                 new ArrayList<>());
426 
427         mCarHeadsUpNotificationQueue.triggerCallback();
428 
429         verify(mCarHeadsUpNotificationQueueCallback).removedFromHeadsUpQueue(any(AlertEntry.class));
430         assertThat(mCarHeadsUpNotificationQueue.mAreNotificationsExpired).isTrue();
431     }
432 
433     @Test
triggerCallback_setHunExpiredFlagToFalse_onHunExpiredAndEmptyQueue()434     public void triggerCallback_setHunExpiredFlagToFalse_onHunExpiredAndEmptyQueue() {
435         mContext.getOrCreateTestableResources().addOverride(
436                 R.bool.config_expireHeadsUpWhenDriving, /* value= */ true);
437         mContext.getOrCreateTestableResources().addOverride(
438                 R.integer.headsup_queue_expire_driving_duration_ms, /* value= */ 1000);
439         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
440         mCarHeadsUpNotificationQueue.setActiveUxRestriction(true); // car is driving
441         Instant now = Instant.now();
442         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
443         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
444                 "key1", "msg"), now.minusMillis(2000).toEpochMilli());
445         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
446         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
447                 new ArrayList<>());
448 
449         mCarHeadsUpNotificationQueue.triggerCallback();
450 
451         assertThat(mCarHeadsUpNotificationQueue.mAreNotificationsExpired).isFalse();
452     }
453 
454     @Test
triggerCallback_setHunRemovalFlagToTrue_onHunExpiredAndEmptyQueue()455     public void triggerCallback_setHunRemovalFlagToTrue_onHunExpiredAndEmptyQueue() {
456         mContext.getOrCreateTestableResources().addOverride(
457                 R.bool.config_expireHeadsUpWhenDriving, /* value= */ true);
458         mContext.getOrCreateTestableResources().addOverride(
459                 R.integer.headsup_queue_expire_driving_duration_ms, /* value= */ 1000);
460         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
461         mCarHeadsUpNotificationQueue.setActiveUxRestriction(true); // car is driving
462         Instant now = Instant.now();
463         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
464         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
465                 "key1", "msg"), now.minusMillis(2000).toEpochMilli());
466         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
467         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
468                 new ArrayList<>());
469 
470         mCarHeadsUpNotificationQueue.triggerCallback();
471 
472         assertThat(mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange).isTrue();
473     }
474 
475     @Test
triggerCallback_sendsNotificationToCurrentUser_onHunExpiredAndEmptyQueue()476     public void triggerCallback_sendsNotificationToCurrentUser_onHunExpiredAndEmptyQueue() {
477         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
478         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
479                 new ArrayList<>());
480         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
481                 "key1", "msg"), /* postTime= */ 1000);
482         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
483         mCarHeadsUpNotificationQueue.mAreNotificationsExpired = true;
484 
485         mCarHeadsUpNotificationQueue.triggerCallback();
486 
487         verify(mNotificationManager).notifyAsUser(anyString(), anyInt(), mNotificationArg.capture(),
488                 eq(UserHandle.of(USER_ID)));
489         assertThat(mNotificationArg.getValue().category).isEqualTo("HUN_QUEUE_INTERNAL");
490     }
491 
492     @Test
triggerCallback_doesNot_expireNotifications_whenDriving()493     public void triggerCallback_doesNot_expireNotifications_whenDriving() {
494         mContext.getOrCreateTestableResources().addOverride(
495                 R.bool.config_expireHeadsUpWhenDriving, /* value= */ false);
496         mContext.getOrCreateTestableResources().addOverride(
497                 R.integer.headsup_queue_expire_driving_duration_ms, /* value= */ 1000);
498         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
499         mCarHeadsUpNotificationQueue.setActiveUxRestriction(true); // car is driving
500         Instant now = Instant.now();
501         mCarHeadsUpNotificationQueue.setClock(Clock.fixed(now, ZoneId.systemDefault()));
502         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
503                 "key1", "msg"), now.minusMillis(3000).toEpochMilli());
504         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
505                 "key2", "msg"), now.minusMillis(500).toEpochMilli());
506         AlertEntry alertEntry3 = new AlertEntry(generateMockStatusBarNotification(
507                 "key3", "msg"), now.toEpochMilli());
508         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
509         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
510         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry3);
511         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
512                 new ArrayList<>());
513 
514         mCarHeadsUpNotificationQueue.triggerCallback();
515 
516         PriorityQueue<String> result = mCarHeadsUpNotificationQueue.getPriorityQueue();
517         verify(mCarHeadsUpNotificationQueueCallback)
518                 .showAsHeadsUp(mAlertEntryArg.capture(),
519                         nullable(NotificationListenerService.RankingMap.class));
520         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
521         assertThat(result.contains("key2")).isTrue();
522         assertThat(result.contains("key3")).isTrue();
523     }
524 
525     @Test
triggerCallback_doesNot_showNotifications_whenAllowlistAppsAreInForeground()526     public void triggerCallback_doesNot_showNotifications_whenAllowlistAppsAreInForeground()
527             throws RemoteException {
528         mContext.getOrCreateTestableResources().addOverride(
529                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
530         mContext.getOrCreateTestableResources().addOverride(
531                 R.array.headsup_throttled_foreground_packages, /* value= */ new String[]{PKG_1});
532         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
533         verify(mActivityTaskManager).registerTaskStackListener(mTaskStackListenerArg.capture());
534         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
535                 "key1", "msg"), /* postTime= */ 1000);
536         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
537         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
538                 new ArrayList<>());
539         ActivityManager.RunningTaskInfo mockRunningTaskInfo =
540                 generateRunningTaskInfo(PKG_1, /* displayAreaFeatureId= */ 111);
541 
542         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo);
543         mCarHeadsUpNotificationQueue.triggerCallback();
544 
545         verify(mCarHeadsUpNotificationQueueCallback, never()).showAsHeadsUp(
546                 any(AlertEntry.class), nullable(NotificationListenerService.RankingMap.class));
547     }
548 
549     @Test
triggerCallback_does_showNotifications_whenAllowlistAppsAreNotInForeground()550     public void triggerCallback_does_showNotifications_whenAllowlistAppsAreNotInForeground()
551             throws RemoteException {
552         mContext.getOrCreateTestableResources().addOverride(
553                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
554         mContext.getOrCreateTestableResources().addOverride(
555                 R.array.headsup_throttled_foreground_packages, /* value= */ new String[]{PKG_1});
556         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
557         verify(mActivityTaskManager).registerTaskStackListener(mTaskStackListenerArg.capture());
558         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
559                 "key1", "msg"), /* postTime= */ 1000);
560         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
561         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
562                 new ArrayList<>());
563         ActivityManager.RunningTaskInfo mockRunningTaskInfo =
564                 generateRunningTaskInfo(PKG_2, /* displayAreaFeatureId= */ 111);
565 
566         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo);
567         mCarHeadsUpNotificationQueue.triggerCallback();
568 
569         verify(mCarHeadsUpNotificationQueueCallback).showAsHeadsUp(
570                 mAlertEntryArg.capture(), nullable(NotificationListenerService.RankingMap.class));
571         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
572     }
573 
574     @Test
nonAllowlistAppInForeground_afterAllowlistApp_callbackScheduled()575     public void nonAllowlistAppInForeground_afterAllowlistApp_callbackScheduled()
576             throws RemoteException {
577         mContext.getOrCreateTestableResources().addOverride(
578                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
579         mContext.getOrCreateTestableResources().addOverride(
580                 R.array.headsup_throttled_foreground_packages, /* value= */ new String[]{PKG_1});
581         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
582         verify(mActivityTaskManager).registerTaskStackListener(mTaskStackListenerArg.capture());
583         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
584                 "key1", "msg"), /* postTime= */ 1000);
585         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
586         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
587                 new ArrayList<>());
588         ActivityManager.RunningTaskInfo mockRunningTaskInfo_AllowlistPkg =
589                 generateRunningTaskInfo(PKG_1, /* displayAreaFeatureId= */ 111);
590         ActivityManager.RunningTaskInfo mockRunningTaskInfo_nonAllowlistPkg =
591                 generateRunningTaskInfo(PKG_2, /* displayAreaFeatureId= */ 111);
592 
593         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo_AllowlistPkg);
594         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo_nonAllowlistPkg);
595 
596         verify(mScheduledExecutorService).schedule(any(Runnable.class), anyLong(),
597                 any(TimeUnit.class));
598     }
599 
600     @Test
nonAllowlistAppInForeground_afterNonAllowlistApp_callbackNotScheduled()601     public void nonAllowlistAppInForeground_afterNonAllowlistApp_callbackNotScheduled()
602             throws RemoteException {
603         mContext.getOrCreateTestableResources().addOverride(
604                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
605         mContext.getOrCreateTestableResources().addOverride(
606                 R.array.headsup_throttled_foreground_packages, /* value= */ new String[]{PKG_1});
607         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
608         verify(mActivityTaskManager).registerTaskStackListener(mTaskStackListenerArg.capture());
609         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
610                 "key1", "msg"), /* postTime= */ 1000);
611         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
612         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
613                 new ArrayList<>());
614         ActivityManager.RunningTaskInfo mockRunningTaskInfo_nonAllowlistPkg_1 =
615                 generateRunningTaskInfo(PKG_2, /* displayAreaFeatureId= */ 111);
616         ActivityManager.RunningTaskInfo mockRunningTaskInfo_nonAllowlistPkg_2 =
617                 generateRunningTaskInfo(PKG_3, /* displayAreaFeatureId= */ 111);
618 
619         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo_nonAllowlistPkg_1);
620         mTaskStackListenerArg.getValue().onTaskMovedToFront(mockRunningTaskInfo_nonAllowlistPkg_2);
621 
622         verify(mScheduledExecutorService, never()).schedule(any(Runnable.class), anyLong(),
623                 any(TimeUnit.class));
624     }
625 
626     @Test
removeFromQueue_returnsFalse_whenNotificationNotInQueue()627     public void removeFromQueue_returnsFalse_whenNotificationNotInQueue() {
628         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
629         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
630                 "key1", "msg"), 1000);
631         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
632                 "key2", "msg"), 2000);
633         AlertEntry alertEntryNotAddedToQueue = new AlertEntry(generateMockStatusBarNotification(
634                 "key3", "msg"), 3000);
635         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
636         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
637 
638         boolean result = mCarHeadsUpNotificationQueue.removeFromQueue(alertEntryNotAddedToQueue);
639 
640         assertThat(result).isFalse();
641     }
642 
643     @Test
removeFromQueue_returnsTrue_whenNotificationInQueue()644     public void removeFromQueue_returnsTrue_whenNotificationInQueue() {
645         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
646         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
647                 "key1", "msg"), 1000);
648         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
649                 "key2", "msg"), 2000);
650         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
651         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
652 
653         boolean result = mCarHeadsUpNotificationQueue.removeFromQueue(alertEntry2);
654 
655         assertThat(result).isTrue();
656     }
657 
658     @Test
releaseQueue_removes_notificationsFromQueue()659     public void releaseQueue_removes_notificationsFromQueue() {
660         mContext.getOrCreateTestableResources().addOverride(
661                 R.bool.config_dismissHeadsUpWhenNotificationCenterOpens, /* value= */ false);
662         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
663         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
664                 "key1", "msg"), /* postTime= */ 1000);
665         AlertEntry alertEntry2 = new AlertEntry(generateMockStatusBarNotification(
666                 "key2", "msg"), /* postTime= */ 2000);
667         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
668         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry2);
669 
670         mCarHeadsUpNotificationQueue.releaseQueue();
671 
672         verify(mCarHeadsUpNotificationQueueCallback, times(2)).removedFromHeadsUpQueue(
673                 mAlertEntryArg.capture());
674         assertThat(mAlertEntryArg.getAllValues().size()).isEqualTo(2);
675     }
676 
677     @Test
releaseQueue_dismiss_activeHUNs()678     public void releaseQueue_dismiss_activeHUNs() {
679         mContext.getOrCreateTestableResources().addOverride(
680                 R.bool.config_dismissHeadsUpWhenNotificationCenterOpens, /* value= */ true);
681         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
682         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
683                 "key1", "msg"), /* postTime= */ 1000);
684         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
685                 new ArrayList<>(Collections.singletonList(alertEntry)));
686 
687         mCarHeadsUpNotificationQueue.releaseQueue();
688 
689         verify(mCarHeadsUpNotificationQueueCallback).dismissHeadsUp(mAlertEntryArg.capture());
690         assertThat(mAlertEntryArg.getValue().getKey()).isEqualTo("key1");
691     }
692 
693     @Test
releaseQueue_doesNot_dismiss_nonDismissibleHUNs()694     public void releaseQueue_doesNot_dismiss_nonDismissibleHUNs() {
695         mContext.getOrCreateTestableResources().addOverride(
696                 R.bool.config_dismissHeadsUpWhenNotificationCenterOpens, /* value= */ true);
697         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
698         AlertEntry alertEntry = new AlertEntry(
699                 generateMockStatusBarNotification("key1", Notification.CATEGORY_CALL,
700                         /* isOngoing= */ true, /* hasFullScreenIntent= */ true),
701                 /* postTime= */ 1000);
702         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
703                 new ArrayList<>(Collections.singletonList(alertEntry)));
704 
705         mCarHeadsUpNotificationQueue.releaseQueue();
706 
707         verify(mCarHeadsUpNotificationQueueCallback, times(0)).removedFromHeadsUpQueue(any());
708     }
709 
710     @Test
onStateChange_internalCategory_hunRemovalFlagTrue_setHunRemovalFlagToFalse()711     public void onStateChange_internalCategory_hunRemovalFlagTrue_setHunRemovalFlagToFalse() {
712         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
713         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
714                 "key1", "HUN_QUEUE_INTERNAL"), 1000);
715         mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange = true;
716 
717         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
718                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
719 
720         assertThat(mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange).isFalse();
721     }
722 
723     @Test
onStateChange_internalCategory_hunRemovalFlagTrue_setHunExpiredToFalse()724     public void onStateChange_internalCategory_hunRemovalFlagTrue_setHunExpiredToFalse() {
725         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
726         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
727                 "key1", "HUN_QUEUE_INTERNAL"), 1000);
728         mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange = true;
729 
730         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
731                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
732 
733         assertThat(mCarHeadsUpNotificationQueue.mAreNotificationsExpired).isFalse();
734     }
735 
736     @Test
onStateChange_internalCategory_hunRemovalFlagTrue_cancelHunForCurrentUser()737     public void onStateChange_internalCategory_hunRemovalFlagTrue_cancelHunForCurrentUser() {
738         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
739         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
740                 "key1", "HUN_QUEUE_INTERNAL"), 1000);
741         mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange = true;
742 
743         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
744                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
745 
746         verify(mNotificationManager).cancelAsUser(anyString(), eq(/* id= */ 2000),
747                 eq(UserHandle.of(USER_ID)));
748     }
749 
750     @Test
onStateChange_notInternalCategory_hunRemovalFlagTrue_notCancelHunForCurrentUser()751     public void onStateChange_notInternalCategory_hunRemovalFlagTrue_notCancelHunForCurrentUser() {
752         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
753         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
754                 "key1", "msg"), 1000);
755         mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange = true;
756 
757         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
758                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
759 
760         verify(mNotificationManager, times(0)).cancelAsUser(anyString(), anyInt(),
761                 any(UserHandle.class));
762     }
763 
764     @Test
onStateChange_notInternalCategory_hunRemovalFlagTrue_doesNotsetHunRemovalFlag()765     public void onStateChange_notInternalCategory_hunRemovalFlagTrue_doesNotsetHunRemovalFlag() {
766         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
767         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
768                 "key1", "msg"), 1000);
769         mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange = true;
770 
771         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
772                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
773 
774         assertThat(mCarHeadsUpNotificationQueue.mCancelInternalNotificationOnStateChange).isTrue();
775     }
776 
777     @Test
onStateChange_dismissed_callbackScheduled()778     public void onStateChange_dismissed_callbackScheduled() {
779         mContext.getOrCreateTestableResources().addOverride(
780                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
781         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
782         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
783                 new ArrayList<>());
784         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
785                 "key1", "msg"), 1000);
786         AlertEntry nextAlertEntry = new AlertEntry(generateMockStatusBarNotification(
787                 "key2", "msg"), 2000);
788         mCarHeadsUpNotificationQueue.addToPriorityQueue(nextAlertEntry);
789 
790         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
791                 CarHeadsUpNotificationManager.HeadsUpState.DISMISSED);
792 
793         verify(mScheduledExecutorService).schedule(any(Runnable.class), anyLong(),
794                 any(TimeUnit.class));
795     }
796 
797     @Test
onStateChange_removedBySender_callbackScheduled()798     public void onStateChange_removedBySender_callbackScheduled() {
799         mContext.getOrCreateTestableResources().addOverride(
800                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
801         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
802         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
803                 new ArrayList<>());
804         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
805                 "key1", "msg"), 1000);
806         AlertEntry nextAlertEntry = new AlertEntry(generateMockStatusBarNotification(
807                 "key2", "msg"), 2000);
808         mCarHeadsUpNotificationQueue.addToPriorityQueue(nextAlertEntry);
809 
810         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
811                 CarHeadsUpNotificationManager.HeadsUpState.REMOVED_BY_SENDER);
812 
813         verify(mScheduledExecutorService).schedule(any(Runnable.class), anyLong(),
814                 any(TimeUnit.class));
815     }
816 
817     @Test
onStateChange_shown_doesNot_showNextNotificationInQueue()818     public void onStateChange_shown_doesNot_showNextNotificationInQueue() {
819         mContext.getOrCreateTestableResources().addOverride(
820                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
821         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
822         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
823                 new ArrayList<>());
824         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
825                 "key1", "msg"), 1000);
826         AlertEntry nextAlertEntry = new AlertEntry(generateMockStatusBarNotification(
827                 "key2", "msg"), 2000);
828         mCarHeadsUpNotificationQueue.addToPriorityQueue(nextAlertEntry);
829 
830         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
831                 CarHeadsUpNotificationManager.HeadsUpState.SHOWN);
832 
833         verify(mCarHeadsUpNotificationQueueCallback, never()).showAsHeadsUp(any(AlertEntry.class),
834                 nullable(NotificationListenerService.RankingMap.class));
835     }
836 
837     @Test
onStateChange_removedFromQueue_does_not_showNextNotificationInQueue()838     public void onStateChange_removedFromQueue_does_not_showNextNotificationInQueue() {
839         mContext.getOrCreateTestableResources().addOverride(
840                 R.bool.config_expireHeadsUpWhenParked, /* value= */ false);
841         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
842         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
843                 new ArrayList<>());
844         AlertEntry alertEntry = new AlertEntry(generateMockStatusBarNotification(
845                 "key1", "msg"), 1000);
846         AlertEntry nextAlertEntry = new AlertEntry(generateMockStatusBarNotification(
847                 "key2", "msg"), 2000);
848         mCarHeadsUpNotificationQueue.addToPriorityQueue(nextAlertEntry);
849 
850         mCarHeadsUpNotificationQueue.onStateChange(alertEntry,
851                 CarHeadsUpNotificationManager.HeadsUpState.REMOVED_FROM_QUEUE);
852 
853         verify(mCarHeadsUpNotificationQueueCallback, never()).showAsHeadsUp(any(AlertEntry.class),
854                 nullable(NotificationListenerService.RankingMap.class));
855     }
856 
857 
858     @Test
getUserNotificationForExpiredHun_parkState_usesParkNotificationTitle()859     public void getUserNotificationForExpiredHun_parkState_usesParkNotificationTitle() {
860         mContext.getOrCreateTestableResources().addOverride(
861                 R.string.hun_suppression_notification_title_park, /* value= */ "test_value");
862         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
863         mCarHeadsUpNotificationQueue.setActiveUxRestriction(false); // car is parked
864 
865         Notification result = mCarHeadsUpNotificationQueue.getUserNotificationForExpiredHun();
866 
867         assertThat(result.extras.getCharSequence(Notification.EXTRA_TITLE).toString()).isEqualTo(
868                 "test_value");
869     }
870 
871     @Test
getUserNotificationForExpiredHun_driveState_usesDriveNotificationTitle()872     public void getUserNotificationForExpiredHun_driveState_usesDriveNotificationTitle() {
873         mContext.getOrCreateTestableResources().addOverride(
874                 R.string.hun_suppression_notification_title_drive, /* value= */ "test_value");
875         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
876         mCarHeadsUpNotificationQueue.setActiveUxRestriction(true); // car is driving
877 
878         Notification result = mCarHeadsUpNotificationQueue.getUserNotificationForExpiredHun();
879 
880         assertThat(result.extras.getCharSequence(Notification.EXTRA_TITLE).toString()).isEqualTo(
881                 "test_value");
882     }
883 
884     @Test
scheduleCallback_schedulesNewTask_whenNoTaskScheduled()885     public void scheduleCallback_schedulesNewTask_whenNoTaskScheduled() {
886         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
887         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
888                 "key1", "msg"), /* postTime= */ 1000);
889         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
890         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
891                 new ArrayList<>());
892         mCarHeadsUpNotificationQueue.mScheduledFuture = null;
893         long delay = 10;
894 
895         mCarHeadsUpNotificationQueue.scheduleCallback(delay);
896 
897         verify(mScheduledExecutorService).schedule(any(Runnable.class),
898                 eq(delay), eq(TimeUnit.MILLISECONDS));
899     }
900 
901     @Test
scheduleCallback_schedulesNewTask_whenShorterTaskScheduled()902     public void scheduleCallback_schedulesNewTask_whenShorterTaskScheduled() {
903         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
904         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
905                 "key1", "msg"), /* postTime= */ 1000);
906         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
907         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
908                 new ArrayList<>());
909         ScheduledFuture<?> mockScheduledFuture = mock(ScheduledFuture.class);
910         when(mockScheduledFuture.getDelay(TimeUnit.MILLISECONDS)).thenReturn(5L);
911         mCarHeadsUpNotificationQueue.mScheduledFuture = mockScheduledFuture;
912         long delay = 10L;
913 
914         mCarHeadsUpNotificationQueue.scheduleCallback(delay);
915 
916         verify(mScheduledExecutorService).schedule(any(Runnable.class),
917                 eq(delay), eq(TimeUnit.MILLISECONDS));
918     }
919 
920     @Test
scheduleCallback_cancelsFutureTask_whenShorterTaskScheduled()921     public void scheduleCallback_cancelsFutureTask_whenShorterTaskScheduled() {
922         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
923         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
924                 "key1", "msg"), /* postTime= */ 1000);
925         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
926         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
927                 new ArrayList<>());
928         ScheduledFuture<?> mockScheduledFuture = mock(ScheduledFuture.class);
929         when(mockScheduledFuture.getDelay(TimeUnit.MILLISECONDS)).thenReturn(5L);
930         mCarHeadsUpNotificationQueue.mScheduledFuture = mockScheduledFuture;
931         long delay = 10L;
932 
933         mCarHeadsUpNotificationQueue.scheduleCallback(delay);
934 
935         verify(mockScheduledFuture).cancel(/* mayInterruptIfRunning= */ true);
936     }
937 
938     @Test
scheduleCallback_doesNotScheduleNewTask_whenLongerTaskScheduled()939     public void scheduleCallback_doesNotScheduleNewTask_whenLongerTaskScheduled() {
940         mCarHeadsUpNotificationQueue = createCarHeadsUpNotificationQueue();
941         AlertEntry alertEntry1 = new AlertEntry(generateMockStatusBarNotification(
942                 "key1", "msg"), /* postTime= */ 1000);
943         mCarHeadsUpNotificationQueue.addToPriorityQueue(alertEntry1);
944         when(mCarHeadsUpNotificationQueueCallback.getActiveHeadsUpNotifications()).thenReturn(
945                 new ArrayList<>());
946         ScheduledFuture<?> mockScheduledFuture = mock(ScheduledFuture.class);
947         when(mockScheduledFuture.getDelay(TimeUnit.MILLISECONDS)).thenReturn(20L);
948         mCarHeadsUpNotificationQueue.mScheduledFuture = mockScheduledFuture;
949         long delay = 10L;
950 
951         mCarHeadsUpNotificationQueue.scheduleCallback(delay);
952 
953         verify(mScheduledExecutorService, never()).schedule(any(Runnable.class), anyLong(),
954                 any(TimeUnit.class));
955     }
956 
generateMockStatusBarNotification(String key, String category)957     private StatusBarNotification generateMockStatusBarNotification(String key, String category) {
958         return generateMockStatusBarNotification(key, category,
959                 /* isOngoing= */ false, /* hasFullScreenIntent= */ false);
960     }
961 
generateMockStatusBarNotification(String key, String category, boolean isOngoing, boolean hasFullScreenIntent)962     private StatusBarNotification generateMockStatusBarNotification(String key, String category,
963             boolean isOngoing, boolean hasFullScreenIntent) {
964         StatusBarNotification sbn = mock(StatusBarNotification.class);
965         Notification.Builder notificationBuilder = new Notification.Builder(mContext,
966                 CHANNEL_ID).setCategory(category).setOngoing(isOngoing);
967         if (hasFullScreenIntent) {
968             notificationBuilder.setFullScreenIntent(mock(PendingIntent.class),
969                     /* highPriority= */ true);
970         }
971         when(sbn.getNotification()).thenReturn(notificationBuilder.build());
972         when(sbn.getKey()).thenReturn(key);
973         return sbn;
974     }
975 
generateRunningTaskInfo(String pkg, int displayAreaFeatureId)976     private ActivityManager.RunningTaskInfo generateRunningTaskInfo(String pkg,
977             int displayAreaFeatureId) {
978         ComponentName componentName = mock(ComponentName.class);
979         when(componentName.getPackageName()).thenReturn(pkg);
980         ActivityManager.RunningTaskInfo mockRunningTaskInfo = mock(
981                 ActivityManager.RunningTaskInfo.class);
982         mockRunningTaskInfo.baseActivity = componentName;
983         mockRunningTaskInfo.displayAreaFeatureId = displayAreaFeatureId;
984         return mockRunningTaskInfo;
985     }
986 }
987