1 /*
2  * Copyright (C) 2018 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 
18 package com.android.systemui.statusbar;
19 
20 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
21 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static junit.framework.Assert.assertFalse;
26 import static junit.framework.Assert.assertTrue;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.mockito.Mockito.spy;
30 
31 import android.app.ActivityManager;
32 import android.app.Notification;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.UserHandle;
36 import android.service.notification.StatusBarNotification;
37 import android.testing.AndroidTestingRunner;
38 import android.testing.TestableLooper;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.systemui.R;
43 import com.android.systemui.SysuiTestCase;
44 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
45 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
46 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
47 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mock;
55 import org.mockito.junit.MockitoJUnit;
56 import org.mockito.junit.MockitoRule;
57 
58 @SmallTest
59 @RunWith(AndroidTestingRunner.class)
60 @TestableLooper.RunWithLooper
61 public class AlertingNotificationManagerTest extends SysuiTestCase {
62     @Rule
63     public MockitoRule rule = MockitoJUnit.rule();
64 
65     private static final String TEST_PACKAGE_NAME = "test";
66     private static final int TEST_UID = 0;
67 
68     protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
69     protected static final int TEST_AUTO_DISMISS_TIME = 600;
70     protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
71     // Number of notifications to use in tests requiring multiple notifications
72     private static final int TEST_NUM_NOTIFICATIONS = 4;
73     protected static final int TEST_TIMEOUT_TIME = 2_000;
74     protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
75 
76     protected Handler mTestHandler;
77     protected boolean mTimedOut = false;
78 
79     @Mock protected ExpandableNotificationRow mRow;
80 
81     private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
82         private AlertEntry mLastCreatedEntry;
83 
TestableAlertingNotificationManager(Handler handler)84         private TestableAlertingNotificationManager(Handler handler) {
85             super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
86             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
87             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
88             mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
89         }
90 
91         @Override
onAlertEntryAdded(AlertEntry alertEntry)92         protected void onAlertEntryAdded(AlertEntry alertEntry) {}
93 
94         @Override
onAlertEntryRemoved(AlertEntry alertEntry)95         protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
96 
97         @Override
createAlertEntry()98         protected AlertEntry createAlertEntry() {
99             mLastCreatedEntry = spy(super.createAlertEntry());
100             return mLastCreatedEntry;
101         }
102 
103         @Override
getContentFlag()104         public int getContentFlag() {
105             return FLAG_CONTENT_VIEW_CONTRACTED;
106         }
107     }
108 
createAlertingNotificationManager()109     protected AlertingNotificationManager createAlertingNotificationManager() {
110         return new TestableAlertingNotificationManager(mTestHandler);
111     }
112 
createSbn(int id, Notification n)113     protected StatusBarNotification createSbn(int id, Notification n) {
114         return new StatusBarNotification(
115                 TEST_PACKAGE_NAME /* pkg */,
116                 TEST_PACKAGE_NAME,
117                 id,
118                 null /* tag */,
119                 TEST_UID,
120                 0 /* initialPid */,
121                 n,
122                 new UserHandle(ActivityManager.getCurrentUser()),
123                 null /* overrideGroupKey */,
124                 0 /* postTime */);
125     }
126 
createSbn(int id, Notification.Builder n)127     protected StatusBarNotification createSbn(int id, Notification.Builder n) {
128         return createSbn(id, n.build());
129     }
130 
createSbn(int id)131     protected StatusBarNotification createSbn(int id) {
132         final Notification.Builder b = new Notification.Builder(mContext, "")
133                 .setSmallIcon(R.drawable.ic_person)
134                 .setContentTitle("Title")
135                 .setContentText("Text");
136         return createSbn(id, b);
137     }
138 
createEntry(int id, Notification n)139     protected NotificationEntry createEntry(int id, Notification n) {
140         return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
141     }
142 
createEntry(int id)143     protected NotificationEntry createEntry(int id) {
144         return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
145     }
146 
verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry, boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition)147     protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
148             boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
149         final Boolean[] wasAlerting = {null};
150         final Runnable checkAlerting =
151                 () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
152 
153         mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
154         mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
155         TestableLooper.get(this).processMessages(2);
156 
157         assertFalse("Test timed out", mTimedOut);
158         if (shouldBeAlerting) {
159             assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
160         } else {
161             assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
162         }
163         assertFalse("Should not still be alerting after processing",
164                 anm.isAlerting(entry.getKey()));
165     }
166 
167     @Before
setUp()168     public void setUp() {
169         mTestHandler = Handler.createAsync(Looper.myLooper());
170 
171         assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
172         assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
173         assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
174     }
175 
176     @After
tearDown()177     public void tearDown() {
178         mTestHandler.removeCallbacksAndMessages(null);
179     }
180 
181     @Test
testShowNotification_addsEntry()182     public void testShowNotification_addsEntry() {
183         final AlertingNotificationManager alm = createAlertingNotificationManager();
184         final NotificationEntry entry = createEntry(/* id = */ 0);
185 
186         alm.showNotification(entry);
187 
188         assertTrue(alm.isAlerting(entry.getKey()));
189         assertTrue(alm.hasNotifications());
190         assertEquals(entry, alm.getEntry(entry.getKey()));
191     }
192 
193     @Test
testShowNotification_autoDismisses()194     public void testShowNotification_autoDismisses() {
195         final AlertingNotificationManager alm = createAlertingNotificationManager();
196         final NotificationEntry entry = createEntry(/* id = */ 0);
197 
198         alm.showNotification(entry);
199 
200         verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
201                 "auto dismiss time");
202 
203         assertFalse(alm.isAlerting(entry.getKey()));
204     }
205 
206     @Test
testRemoveNotification_removeDeferred()207     public void testRemoveNotification_removeDeferred() {
208         final AlertingNotificationManager alm = createAlertingNotificationManager();
209         final NotificationEntry entry = createEntry(/* id = */ 0);
210 
211         alm.showNotification(entry);
212 
213         // Try to remove but defer, since the notification has not been shown long enough.
214         final boolean removedImmediately = alm.removeNotification(entry.getKey(),
215                 false /* releaseImmediately */);
216 
217         assertFalse(removedImmediately);
218         assertTrue(alm.isAlerting(entry.getKey()));
219     }
220 
221     @Test
testRemoveNotification_forceRemove()222     public void testRemoveNotification_forceRemove() {
223         final AlertingNotificationManager alm = createAlertingNotificationManager();
224         final NotificationEntry entry = createEntry(/* id = */ 0);
225 
226         alm.showNotification(entry);
227 
228         // Remove forcibly with releaseImmediately = true.
229         final boolean removedImmediately = alm.removeNotification(entry.getKey(),
230                 true /* releaseImmediately */);
231 
232         assertTrue(removedImmediately);
233         assertFalse(alm.isAlerting(entry.getKey()));
234     }
235 
236     @Test
testReleaseAllImmediately()237     public void testReleaseAllImmediately() {
238         final AlertingNotificationManager alm = createAlertingNotificationManager();
239         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
240             final NotificationEntry entry = createEntry(i);
241             entry.setRow(mRow);
242             alm.showNotification(entry);
243         }
244 
245         alm.releaseAllImmediately();
246 
247         assertEquals(0, alm.getAllEntries().count());
248     }
249 
250     @Test
testCanRemoveImmediately_notShownLongEnough()251     public void testCanRemoveImmediately_notShownLongEnough() {
252         final AlertingNotificationManager alm = createAlertingNotificationManager();
253         final NotificationEntry entry = createEntry(/* id = */ 0);
254 
255         alm.showNotification(entry);
256 
257         // The entry has just been added so we should not remove immediately.
258         assertFalse(alm.canRemoveImmediately(entry.getKey()));
259     }
260 }
261