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 package com.android.settings.applications;
18 
19 import static android.text.format.DateUtils.DAY_IN_MILLIS;
20 
21 import static com.android.settings.applications.AppStateNotificationBridge
22         .FILTER_APP_NOTIFICATION_BLOCKED;
23 import static com.android.settings.applications.AppStateNotificationBridge
24         .FILTER_APP_NOTIFICATION_FREQUENCY;
25 import static com.android.settings.applications.AppStateNotificationBridge
26         .FILTER_APP_NOTIFICATION_RECENCY;
27 import static com.android.settings.applications.AppStateNotificationBridge
28         .FREQUENCY_NOTIFICATION_COMPARATOR;
29 import static com.android.settings.applications.AppStateNotificationBridge
30         .RECENT_NOTIFICATION_COMPARATOR;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertTrue;
36 import static org.mockito.ArgumentMatchers.any;
37 import static org.mockito.ArgumentMatchers.anyInt;
38 import static org.mockito.ArgumentMatchers.anyLong;
39 import static org.mockito.ArgumentMatchers.anyString;
40 import static org.mockito.ArgumentMatchers.eq;
41 import static org.mockito.Mockito.mock;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.when;
44 
45 import android.app.usage.IUsageStatsManager;
46 import android.app.usage.UsageEvents;
47 import android.app.usage.UsageEvents.Event;
48 import android.content.Context;
49 import android.content.pm.ApplicationInfo;
50 import android.os.Looper;
51 import android.os.Parcel;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.view.ViewGroup;
56 import android.widget.CompoundButton;
57 import android.widget.Switch;
58 
59 import com.android.settings.R;
60 import com.android.settings.applications.AppStateNotificationBridge.NotificationsSentState;
61 import com.android.settings.notification.NotificationBackend;
62 import com.android.settingslib.applications.ApplicationsState;
63 import com.android.settingslib.applications.ApplicationsState.AppEntry;
64 
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 import org.mockito.Mock;
69 import org.mockito.MockitoAnnotations;
70 import org.robolectric.RobolectricTestRunner;
71 import org.robolectric.RuntimeEnvironment;
72 
73 import java.util.ArrayList;
74 import java.util.List;
75 import java.util.Map;
76 
77 @RunWith(RobolectricTestRunner.class)
78 public class AppStateNotificationBridgeTest {
79 
80     private static String PKG1 = "pkg1";
81     private static String PKG2 = "pkg2";
82 
83     @Mock
84     private ApplicationsState.Session mSession;
85     @Mock
86     private ApplicationsState mState;
87     @Mock
88     private IUsageStatsManager mUsageStats;
89     @Mock
90     private UserManager mUserManager;
91     @Mock
92     private NotificationBackend mBackend;
93     private Context mContext;
94     private AppStateNotificationBridge mBridge;
95 
96     @Before
setUp()97     public void setUp() {
98         MockitoAnnotations.initMocks(this);
99         when(mState.newSession(any())).thenReturn(mSession);
100         when(mState.getBackgroundLooper()).thenReturn(mock(Looper.class));
101         when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(true);
102         when(mBackend.isSystemApp(any(), any())).thenReturn(true);
103         // most tests assume no work profile
104         when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
105         mContext = RuntimeEnvironment.application.getApplicationContext();
106 
107         mBridge = new AppStateNotificationBridge(mContext, mState,
108                 mock(AppStateBaseBridge.Callback.class), mUsageStats, mUserManager, mBackend);
109     }
110 
getMockAppEntry(String pkg)111     private AppEntry getMockAppEntry(String pkg) {
112         AppEntry entry = mock(AppEntry.class);
113         entry.info = mock(ApplicationInfo.class);
114         entry.info.packageName = pkg;
115         return entry;
116     }
117 
getUsageEvents(List<Event> events)118     private UsageEvents getUsageEvents(List<Event> events) {
119         UsageEvents usageEvents = new UsageEvents(events, new String[] {PKG1, PKG2});
120         Parcel parcel = Parcel.obtain();
121         parcel.setDataPosition(0);
122         usageEvents.writeToParcel(parcel, 0);
123         parcel.setDataPosition(0);
124         return UsageEvents.CREATOR.createFromParcel(parcel);
125     }
126 
127     @Test
testGetAggregatedUsageEvents_noEvents()128     public void testGetAggregatedUsageEvents_noEvents() throws Exception {
129         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
130                 .thenReturn(mock(UsageEvents.class));
131 
132         assertThat(mBridge.getAggregatedUsageEvents()).isEmpty();
133     }
134 
135     @Test
testGetAggregatedUsageEvents_onlyNotificationEvents()136     public void testGetAggregatedUsageEvents_onlyNotificationEvents() throws Exception {
137         List<Event> events = new ArrayList<>();
138         Event good = new Event();
139         good.mEventType = Event.NOTIFICATION_INTERRUPTION;
140         good.mPackage = PKG1;
141         good.mTimeStamp = 1;
142         events.add(good);
143         Event bad = new Event();
144         bad.mEventType = Event.CHOOSER_ACTION;
145         bad.mPackage = PKG1;
146         bad.mTimeStamp = 2;
147         events.add(bad);
148 
149         UsageEvents usageEvents = getUsageEvents(events);
150         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
151                 .thenReturn(usageEvents);
152 
153         Map<String, NotificationsSentState> map = mBridge.getAggregatedUsageEvents();
154         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG1)).sentCount).isEqualTo(1);
155     }
156 
157     @Test
testGetAggregatedUsageEvents_multipleEventsAgg()158     public void testGetAggregatedUsageEvents_multipleEventsAgg() throws Exception {
159         List<Event> events = new ArrayList<>();
160         Event good = new Event();
161         good.mEventType = Event.NOTIFICATION_INTERRUPTION;
162         good.mPackage = PKG1;
163         good.mTimeStamp = 6;
164         events.add(good);
165         Event good1 = new Event();
166         good1.mEventType = Event.NOTIFICATION_INTERRUPTION;
167         good1.mPackage = PKG1;
168         good1.mTimeStamp = 1;
169         events.add(good1);
170 
171         UsageEvents usageEvents = getUsageEvents(events);
172         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
173                 .thenReturn(usageEvents);
174 
175         Map<String, NotificationsSentState> map  = mBridge.getAggregatedUsageEvents();
176         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG1)).sentCount).isEqualTo(2);
177         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG1)).lastSent).isEqualTo(6);
178     }
179 
180     @Test
testGetAggregatedUsageEvents_multiplePkgs()181     public void testGetAggregatedUsageEvents_multiplePkgs() throws Exception {
182         List<Event> events = new ArrayList<>();
183         Event good = new Event();
184         good.mEventType = Event.NOTIFICATION_INTERRUPTION;
185         good.mPackage = PKG1;
186         good.mTimeStamp = 6;
187         events.add(good);
188         Event good1 = new Event();
189         good1.mEventType = Event.NOTIFICATION_INTERRUPTION;
190         good1.mPackage = PKG2;
191         good1.mTimeStamp = 1;
192         events.add(good1);
193 
194         UsageEvents usageEvents = getUsageEvents(events);
195         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
196                 .thenReturn(usageEvents);
197 
198         Map<String, NotificationsSentState> map
199                 = mBridge.getAggregatedUsageEvents();
200         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG1)).sentCount).isEqualTo(1);
201         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG2)).sentCount).isEqualTo(1);
202         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG1)).lastSent).isEqualTo(6);
203         assertThat(map.get(AppStateNotificationBridge.getKey(0, PKG2)).lastSent).isEqualTo(1);
204     }
205 
206     @Test
testLoadAllExtraInfo_noEvents()207     public void testLoadAllExtraInfo_noEvents() throws RemoteException {
208         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
209                 .thenReturn(mock(UsageEvents.class));
210         ArrayList<AppEntry> apps = new ArrayList<>();
211         apps.add(getMockAppEntry(PKG1));
212         when(mSession.getAllApps()).thenReturn(apps);
213 
214         mBridge.loadAllExtraInfo();
215         // extra info should exist and blocked status should be populated
216         assertThat(apps.get(0).extraInfo).isNotNull();
217         verify(mBackend).getNotificationsBanned(PKG1, 0);
218         // but the recent/frequent counts should be 0 so they don't appear on those screens
219         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(0);
220         assertThat(((NotificationsSentState) apps.get(0).extraInfo).lastSent).isEqualTo(0);
221     }
222 
223     @Test
testLoadAllExtraInfo_multipleEventsAgg()224     public void testLoadAllExtraInfo_multipleEventsAgg() throws RemoteException {
225         List<Event> events = new ArrayList<>();
226         for (int i = 0; i < 7; i++) {
227             Event good = new Event();
228             good.mEventType = Event.NOTIFICATION_INTERRUPTION;
229             good.mPackage = PKG1;
230             good.mTimeStamp = i;
231             events.add(good);
232         }
233 
234         UsageEvents usageEvents = getUsageEvents(events);
235         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
236                 .thenReturn(usageEvents);
237 
238         ArrayList<AppEntry> apps = new ArrayList<>();
239         apps.add(getMockAppEntry(PKG1));
240         when(mSession.getAllApps()).thenReturn(apps);
241 
242         mBridge.loadAllExtraInfo();
243         assertThat(((NotificationsSentState) apps.get(0).extraInfo).sentCount).isEqualTo(7);
244         assertThat(((NotificationsSentState) apps.get(0).extraInfo).lastSent).isEqualTo(6);
245         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(1);
246         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0);
247         assertThat(((NotificationsSentState) apps.get(0).extraInfo).blocked).isTrue();
248         assertThat(((NotificationsSentState) apps.get(0).extraInfo).systemApp).isTrue();
249         assertThat(((NotificationsSentState) apps.get(0).extraInfo).blockable).isTrue();
250     }
251 
252     @Test
testLoadAllExtraInfo_multiplePkgs()253     public void testLoadAllExtraInfo_multiplePkgs() throws RemoteException {
254         List<Event> events = new ArrayList<>();
255         for (int i = 0; i < 8; i++) {
256             Event good = new Event();
257             good.mEventType = Event.NOTIFICATION_INTERRUPTION;
258             good.mPackage = PKG1;
259             good.mTimeStamp = i;
260             events.add(good);
261         }
262         Event good1 = new Event();
263         good1.mEventType = Event.NOTIFICATION_INTERRUPTION;
264         good1.mPackage = PKG2;
265         good1.mTimeStamp = 1;
266         events.add(good1);
267 
268         UsageEvents usageEvents = getUsageEvents(events);
269         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
270                 .thenReturn(usageEvents);
271 
272         ArrayList<AppEntry> apps = new ArrayList<>();
273         apps.add(getMockAppEntry(PKG1));
274         apps.add(getMockAppEntry(PKG2));
275         when(mSession.getAllApps()).thenReturn(apps);
276 
277         mBridge.loadAllExtraInfo();
278         assertThat(((NotificationsSentState) apps.get(0).extraInfo).sentCount).isEqualTo(8);
279         assertThat(((NotificationsSentState) apps.get(0).extraInfo).lastSent).isEqualTo(7);
280         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0);
281         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(1);
282 
283         assertThat(((NotificationsSentState) apps.get(1).extraInfo).sentCount).isEqualTo(1);
284         assertThat(((NotificationsSentState) apps.get(1).extraInfo).lastSent).isEqualTo(1);
285         assertThat(((NotificationsSentState) apps.get(1).extraInfo).avgSentWeekly).isEqualTo(1);
286         assertThat(((NotificationsSentState) apps.get(1).extraInfo).avgSentDaily).isEqualTo(0);
287     }
288 
289     @Test
testLoadAllExtraInfo_multipleUsers()290     public void testLoadAllExtraInfo_multipleUsers() throws RemoteException {
291         // has work profile
292         when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{1});
293         mBridge = new AppStateNotificationBridge(mContext, mState,
294                 mock(AppStateBaseBridge.Callback.class), mUsageStats, mUserManager, mBackend);
295 
296         List<Event> eventsProfileOwner = new ArrayList<>();
297         for (int i = 0; i < 8; i++) {
298             Event good = new Event();
299             good.mEventType = Event.NOTIFICATION_INTERRUPTION;
300             good.mPackage = PKG1;
301             good.mTimeStamp = i;
302             eventsProfileOwner.add(good);
303         }
304 
305         List<Event> eventsProfile = new ArrayList<>();
306         for (int i = 0; i < 4; i++) {
307             Event good = new Event();
308             good.mEventType = Event.NOTIFICATION_INTERRUPTION;
309             good.mPackage = PKG1;
310             good.mTimeStamp = i;
311             eventsProfile.add(good);
312         }
313 
314         UsageEvents usageEventsOwner = getUsageEvents(eventsProfileOwner);
315         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), eq(0), anyString()))
316                 .thenReturn(usageEventsOwner);
317 
318         UsageEvents usageEventsProfile = getUsageEvents(eventsProfile);
319         when(mUsageStats.queryEventsForUser(anyLong(), anyLong(), eq(1), anyString()))
320                 .thenReturn(usageEventsProfile);
321 
322         ArrayList<AppEntry> apps = new ArrayList<>();
323         AppEntry owner = getMockAppEntry(PKG1);
324         owner.info.uid = 1;
325         apps.add(owner);
326 
327         AppEntry profile = getMockAppEntry(PKG1);
328         profile.info.uid = UserHandle.PER_USER_RANGE + 1;
329         apps.add(profile);
330         when(mSession.getAllApps()).thenReturn(apps);
331 
332         mBridge.loadAllExtraInfo();
333 
334         assertThat(((NotificationsSentState) apps.get(0).extraInfo).sentCount).isEqualTo(8);
335         assertThat(((NotificationsSentState) apps.get(0).extraInfo).lastSent).isEqualTo(7);
336         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0);
337         assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(1);
338 
339         assertThat(((NotificationsSentState) apps.get(1).extraInfo).sentCount).isEqualTo(4);
340         assertThat(((NotificationsSentState) apps.get(1).extraInfo).lastSent).isEqualTo(3);
341         assertThat(((NotificationsSentState) apps.get(1).extraInfo).avgSentWeekly).isEqualTo(4);
342         assertThat(((NotificationsSentState) apps.get(1).extraInfo).avgSentDaily).isEqualTo(1);
343     }
344 
345     @Test
testUpdateExtraInfo_noEvents()346     public void testUpdateExtraInfo_noEvents() throws RemoteException {
347         when(mUsageStats.queryEventsForPackageForUser(
348                 anyLong(), anyLong(), anyInt(), anyString(), anyString()))
349                 .thenReturn(mock(UsageEvents.class));
350         AppEntry entry = getMockAppEntry(PKG1);
351 
352         mBridge.updateExtraInfo(entry, "", 0);
353         assertThat(entry.extraInfo).isNull();
354     }
355 
356     @Test
testUpdateExtraInfo_multipleEventsAgg()357     public void testUpdateExtraInfo_multipleEventsAgg() throws RemoteException {
358         List<Event> events = new ArrayList<>();
359         for (int i = 0; i < 13; i++) {
360             Event good = new Event();
361             good.mEventType = Event.NOTIFICATION_INTERRUPTION;
362             good.mPackage = PKG1;
363             good.mTimeStamp = i;
364             events.add(good);
365         }
366 
367         UsageEvents usageEvents = getUsageEvents(events);
368         when(mUsageStats.queryEventsForPackageForUser(
369                 anyLong(), anyLong(), anyInt(), anyString(), anyString())).thenReturn(usageEvents);
370 
371         AppEntry entry = getMockAppEntry(PKG1);
372         mBridge.updateExtraInfo(entry, "", 0);
373 
374         assertThat(((NotificationsSentState) entry.extraInfo).sentCount).isEqualTo(13);
375         assertThat(((NotificationsSentState) entry.extraInfo).lastSent).isEqualTo(12);
376         assertThat(((NotificationsSentState) entry.extraInfo).avgSentDaily).isEqualTo(2);
377         assertThat(((NotificationsSentState) entry.extraInfo).avgSentWeekly).isEqualTo(0);
378         assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue();
379         assertThat(((NotificationsSentState) entry.extraInfo).systemApp).isTrue();
380         assertThat(((NotificationsSentState) entry.extraInfo).blockable).isTrue();
381     }
382 
383     @Test
testSummary_recency()384     public void testSummary_recency() {
385         NotificationsSentState neverSent = new NotificationsSentState();
386         NotificationsSentState sent = new NotificationsSentState();
387         sent.lastSent = System.currentTimeMillis() - (2 * DAY_IN_MILLIS);
388 
389         assertThat(AppStateNotificationBridge.getSummary(
390                 mContext, neverSent, R.id.sort_order_recent_notification)).isEqualTo(
391                         mContext.getString(R.string.notifications_sent_never));
392         assertThat(AppStateNotificationBridge.getSummary(
393                 mContext, sent, R.id.sort_order_recent_notification).toString()).contains("2");
394     }
395 
396     @Test
testSummary_frequency()397     public void testSummary_frequency() {
398         NotificationsSentState sentRarely = new NotificationsSentState();
399         sentRarely.avgSentWeekly = 1;
400         NotificationsSentState sentOften = new NotificationsSentState();
401         sentOften.avgSentDaily = 8;
402 
403         assertThat(AppStateNotificationBridge.getSummary(
404                 mContext, sentRarely, R.id.sort_order_frequent_notification).toString())
405                 .contains("1");
406         assertThat(AppStateNotificationBridge.getSummary(
407                 mContext, sentRarely, R.id.sort_order_frequent_notification).toString())
408                 .contains("notification ");
409         assertThat(AppStateNotificationBridge.getSummary(
410                 mContext, sentRarely, R.id.sort_order_frequent_notification).toString())
411                 .contains("week");
412         assertThat(AppStateNotificationBridge.getSummary(
413                 mContext, sentOften, R.id.sort_order_frequent_notification).toString())
414                 .contains("8");
415         assertThat(AppStateNotificationBridge.getSummary(
416                 mContext, sentOften, R.id.sort_order_frequent_notification).toString())
417                 .contains("notifications");
418         assertThat(AppStateNotificationBridge.getSummary(
419                 mContext, sentOften, R.id.sort_order_frequent_notification).toString())
420                 .contains("day");
421     }
422 
423     @Test
testSummary_alpha()424     public void testSummary_alpha() {
425         NotificationsSentState sentRarely = new NotificationsSentState();
426         sentRarely.avgSentWeekly = 1;
427         assertThat(AppStateNotificationBridge.getSummary(
428                 mContext, sentRarely, R.id.sort_order_alpha).toString())
429                 .isEqualTo("");
430     }
431 
432     @Test
testFilterRecency()433     public void testFilterRecency() {
434         NotificationsSentState allowState = new NotificationsSentState();
435         allowState.lastSent = 1;
436         AppEntry allow = mock(AppEntry.class);
437         allow.extraInfo = allowState;
438 
439 
440         assertTrue(FILTER_APP_NOTIFICATION_RECENCY.filterApp(allow));
441 
442         NotificationsSentState denyState = new NotificationsSentState();
443         denyState.lastSent = 0;
444         AppEntry deny = mock(AppEntry.class);
445         deny.extraInfo = denyState;
446 
447         assertFalse(FILTER_APP_NOTIFICATION_RECENCY.filterApp(deny));
448     }
449 
450     @Test
testFilterFrequency()451     public void testFilterFrequency() {
452         NotificationsSentState allowState = new NotificationsSentState();
453         allowState.sentCount = 1;
454         AppEntry allow = mock(AppEntry.class);
455         allow.extraInfo = allowState;
456 
457         assertTrue(FILTER_APP_NOTIFICATION_FREQUENCY.filterApp(allow));
458 
459         NotificationsSentState denyState = new NotificationsSentState();
460         denyState.sentCount = 0;
461         AppEntry deny = mock(AppEntry.class);
462         deny.extraInfo = denyState;
463 
464         assertFalse(FILTER_APP_NOTIFICATION_FREQUENCY.filterApp(deny));
465     }
466 
467     @Test
testFilterBlocked()468     public void testFilterBlocked() {
469         NotificationsSentState allowState = new NotificationsSentState();
470         allowState.blocked = true;
471         AppEntry allow = mock(AppEntry.class);
472         allow.extraInfo = allowState;
473 
474         assertTrue(FILTER_APP_NOTIFICATION_BLOCKED.filterApp(allow));
475 
476         NotificationsSentState denyState = new NotificationsSentState();
477         denyState.blocked = false;
478         AppEntry deny = mock(AppEntry.class);
479         deny.extraInfo = denyState;
480 
481         assertFalse(FILTER_APP_NOTIFICATION_BLOCKED.filterApp(deny));
482     }
483 
484     @Test
testComparators_nullsNoCrash()485     public void testComparators_nullsNoCrash() {
486         List<AppEntry> entries = new ArrayList<>();
487         AppEntry a = mock(AppEntry.class);
488         a.label = "1";
489         AppEntry b = mock(AppEntry.class);
490         b.label = "2";
491         entries.add(a);
492         entries.add(b);
493 
494         entries.sort(RECENT_NOTIFICATION_COMPARATOR);
495         entries.sort(FREQUENCY_NOTIFICATION_COMPARATOR);
496     }
497 
498     @Test
testRecencyComparator()499     public void testRecencyComparator() {
500         List<AppEntry> entries = new ArrayList<>();
501 
502         NotificationsSentState earlier = new NotificationsSentState();
503         earlier.lastSent = 1;
504         AppEntry earlyEntry = mock(AppEntry.class);
505         earlyEntry.extraInfo = earlier;
506         entries.add(earlyEntry);
507 
508         NotificationsSentState later = new NotificationsSentState();
509         later.lastSent = 8;
510         AppEntry lateEntry = mock(AppEntry.class);
511         lateEntry.extraInfo = later;
512         entries.add(lateEntry);
513 
514         entries.sort(RECENT_NOTIFICATION_COMPARATOR);
515 
516         assertThat(entries).containsExactly(lateEntry, earlyEntry);
517     }
518 
519     @Test
testFrequencyComparator()520     public void testFrequencyComparator() {
521         List<AppEntry> entries = new ArrayList<>();
522 
523         NotificationsSentState notFrequentWeekly = new NotificationsSentState();
524         notFrequentWeekly.sentCount = 2;
525         AppEntry notFrequentWeeklyEntry = mock(AppEntry.class);
526         notFrequentWeeklyEntry.extraInfo = notFrequentWeekly;
527         entries.add(notFrequentWeeklyEntry);
528 
529         NotificationsSentState notFrequentDaily = new NotificationsSentState();
530         notFrequentDaily.sentCount = 7;
531         AppEntry notFrequentDailyEntry = mock(AppEntry.class);
532         notFrequentDailyEntry.extraInfo = notFrequentDaily;
533         entries.add(notFrequentDailyEntry);
534 
535         NotificationsSentState veryFrequentWeekly = new NotificationsSentState();
536         veryFrequentWeekly.sentCount = 6;
537         AppEntry veryFrequentWeeklyEntry = mock(AppEntry.class);
538         veryFrequentWeeklyEntry.extraInfo = veryFrequentWeekly;
539         entries.add(veryFrequentWeeklyEntry);
540 
541         NotificationsSentState veryFrequentDaily = new NotificationsSentState();
542         veryFrequentDaily.sentCount = 19;
543         AppEntry veryFrequentDailyEntry = mock(AppEntry.class);
544         veryFrequentDailyEntry.extraInfo = veryFrequentDaily;
545         entries.add(veryFrequentDailyEntry);
546 
547         entries.sort(FREQUENCY_NOTIFICATION_COMPARATOR);
548 
549         assertThat(entries).containsExactly(veryFrequentDailyEntry, notFrequentDailyEntry,
550                 veryFrequentWeeklyEntry, notFrequentWeeklyEntry);
551     }
552 
553     @Test
testSwitchOnChangeListener()554     public void testSwitchOnChangeListener() {
555         Switch toggle = mock(Switch.class);
556         when(toggle.isChecked()).thenReturn(true);
557         when(toggle.isEnabled()).thenReturn(true);
558 
559         AppEntry entry = mock(AppEntry.class);
560         entry.info = new ApplicationInfo();
561         entry.info.packageName = "pkg";
562         entry.info.uid = 1356;
563         entry.extraInfo = new NotificationsSentState();
564 
565         CompoundButton.OnCheckedChangeListener listener = mBridge.getSwitchOnCheckedListener(entry);
566         listener.onCheckedChanged(toggle, true);
567 
568         verify(mBackend).setNotificationsEnabledForPackage(
569                 entry.info.packageName, entry.info.uid, true);
570         assertThat(((NotificationsSentState) entry.extraInfo).blocked).isFalse();
571     }
572 
573     @Test
testSwitchViews_nullDoesNotCrash()574     public void testSwitchViews_nullDoesNotCrash() {
575         AppStateNotificationBridge.enableSwitch(null);
576         AppStateNotificationBridge.checkSwitch(null);
577     }
578 }
579