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