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