1/* 2 * Copyright (C) 2015 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.server.telecom.tests; 18 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.ComponentName; 23import android.content.ContentProvider; 24import android.content.Context; 25import android.content.IContentProvider; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.database.Cursor; 29import android.net.Uri; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.ICancellationSignal; 33import android.os.Looper; 34import android.os.UserHandle; 35import android.provider.CallLog; 36import android.telecom.PhoneAccount; 37import android.telecom.PhoneAccountHandle; 38import android.telecom.TelecomManager; 39import android.telephony.TelephonyManager; 40import android.test.suitebuilder.annotation.SmallTest; 41 42import com.android.internal.telephony.CallerInfo; 43import com.android.server.telecom.CallerInfoLookupHelper; 44import com.android.server.telecom.Constants; 45import com.android.server.telecom.DefaultDialerCache; 46import com.android.server.telecom.MissedCallNotifier; 47import com.android.server.telecom.PhoneAccountRegistrar; 48import com.android.server.telecom.TelecomBroadcastIntentProcessor; 49import com.android.server.telecom.TelecomSystem; 50import com.android.server.telecom.components.TelecomBroadcastReceiver; 51import com.android.server.telecom.ui.MissedCallNotifierImpl; 52import com.android.server.telecom.ui.MissedCallNotifierImpl.NotificationBuilderFactory; 53 54import org.junit.After; 55import org.junit.Before; 56import org.junit.Test; 57import org.junit.runner.RunWith; 58import org.junit.runners.JUnit4; 59import org.mockito.ArgumentCaptor; 60import org.mockito.Mock; 61import org.mockito.MockitoAnnotations; 62 63import java.util.Arrays; 64import java.util.HashSet; 65import java.util.LinkedList; 66import java.util.List; 67 68import static org.junit.Assert.assertNotNull; 69import static org.junit.Assert.assertNull; 70import static org.mockito.ArgumentMatchers.nullable; 71import static org.mockito.Matchers.any; 72import static org.mockito.Matchers.anyString; 73import static org.mockito.Matchers.eq; 74import static org.mockito.Matchers.isNull; 75import static org.mockito.Mockito.doReturn; 76import static org.mockito.Mockito.mock; 77import static org.mockito.Mockito.never; 78import static org.mockito.Mockito.spy; 79import static org.mockito.Mockito.timeout; 80import static org.mockito.Mockito.times; 81import static org.mockito.Mockito.verify; 82import static org.mockito.Mockito.when; 83 84@RunWith(JUnit4.class) 85public class MissedCallNotifierImplTest extends TelecomTestCase { 86 87 private static class MockMissedCallCursorBuilder { 88 private class CallLogRow { 89 String number; 90 int presentation; 91 long date; 92 93 public CallLogRow(String number, int presentation, long date) { 94 this.number = number; 95 this.presentation = presentation; 96 this.date = date; 97 } 98 } 99 100 private List<CallLogRow> mRows; 101 102 MockMissedCallCursorBuilder() { 103 mRows = new LinkedList<>(); 104 mRows.add(null); 105 } 106 107 public MockMissedCallCursorBuilder addEntry(String number, int presentation, long date) { 108 mRows.add(new CallLogRow(number, presentation, date)); 109 return this; 110 } 111 112 public Cursor build() { 113 Cursor c = mock(Cursor.class); 114 when(c.moveToNext()).thenAnswer(unused -> { 115 mRows.remove(0); 116 return mRows.size() > 0; 117 }); 118 when(c.getString(MissedCallNotifierImpl.CALL_LOG_COLUMN_NUMBER)) 119 .thenAnswer(unused -> mRows.get(0).number); 120 when(c.getInt(MissedCallNotifierImpl.CALL_LOG_COLUMN_NUMBER_PRESENTATION)) 121 .thenAnswer(unused -> mRows.get(0).presentation); 122 when(c.getLong(MissedCallNotifierImpl.CALL_LOG_COLUMN_DATE)) 123 .thenAnswer(unused -> mRows.get(0).date); 124 return c; 125 } 126 } 127 128 private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620"); 129 private static final Uri SIP_CALL_HANDLE = Uri.parse("sip:testaddress@testdomain.com"); 130 private static final String CALLER_NAME = "Fake Name"; 131 private static final String MISSED_CALL_TITLE = "Missed Call"; 132 private static final String MISSED_CALLS_TITLE = "Missed Calls"; 133 private static final String MISSED_CALLS_MSG = "%s missed calls"; 134 private static final String USER_CALL_ACTIVITY_LABEL = "Phone"; 135 136 private static final int REQUEST_ID = 0; 137 private static final long CALL_TIMESTAMP; 138 static { 139 CALL_TIMESTAMP = System.currentTimeMillis() - 60 * 1000 * 5; 140 } 141 142 private static final UserHandle PRIMARY_USER = UserHandle.of(0); 143 private static final UserHandle SECONARY_USER = UserHandle.of(12); 144 private static final int NO_CAPABILITY = 0; 145 private static final int TEST_TIMEOUT = 1000; 146 147 @Mock 148 private NotificationManager mNotificationManager; 149 150 @Mock 151 private PhoneAccountRegistrar mPhoneAccountRegistrar; 152 153 @Mock 154 private TelecomManager mTelecomManager; 155 156 @Mock TelecomSystem mTelecomSystem; 157 @Mock private DefaultDialerCache mDefaultDialerCache; 158 159 @Override 160 @Before 161 public void setUp() throws Exception { 162 super.setUp(); 163 MockitoAnnotations.initMocks(this); 164 165 mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); 166 mNotificationManager = (NotificationManager) mContext.getSystemService( 167 Context.NOTIFICATION_SERVICE); 168 TelephonyManager fakeTelephonyManager = (TelephonyManager) mContext.getSystemService( 169 Context.TELEPHONY_SERVICE); 170 when(fakeTelephonyManager.getNetworkCountryIso()).thenReturn("US"); 171 doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo(); 172 doReturn("com.android.server.telecom.tests").when(mContext).getPackageName(); 173 174 mComponentContextFixture.putResource(R.string.notification_missedCallTitle, 175 MISSED_CALL_TITLE); 176 mComponentContextFixture.putResource(R.string.notification_missedCallsTitle, 177 MISSED_CALLS_TITLE); 178 mComponentContextFixture.putResource(R.string.notification_missedCallsMsg, 179 MISSED_CALLS_MSG); 180 mComponentContextFixture.putResource(R.string.userCallActivityLabel, 181 USER_CALL_ACTIVITY_LABEL); 182 mComponentContextFixture.setTelecomManager(mTelecomManager); 183 } 184 185 @Override 186 @After 187 public void tearDown() throws Exception { 188 TelecomSystem.setInstance(null); 189 when(mTelecomSystem.isBootComplete()).thenReturn(false); 190 } 191 192 @SmallTest 193 @Test 194 public void testCancelNotificationInPrimaryUser() { 195 cancelNotificationTestInternal(PRIMARY_USER); 196 } 197 198 @SmallTest 199 @Test 200 public void testCancelNotificationInSecondaryUser() { 201 cancelNotificationTestInternal(SECONARY_USER); 202 } 203 204 private void cancelNotificationTestInternal(UserHandle userHandle) { 205 Notification.Builder builder1 = makeNotificationBuilder("builder1"); 206 Notification.Builder builder2 = makeNotificationBuilder("builder2"); 207 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 208 makeNotificationBuilderFactory(builder1, builder1, builder2, builder2); 209 210 MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory, 211 PRIMARY_USER); 212 PhoneAccount phoneAccount = makePhoneAccount(userHandle, NO_CAPABILITY); 213 MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, 214 CALL_TIMESTAMP, phoneAccount.getAccountHandle()); 215 216 missedCallNotifier.showMissedCallNotification(fakeCall); 217 missedCallNotifier.clearMissedCalls(userHandle); 218 missedCallNotifier.showMissedCallNotification(fakeCall); 219 220 ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass( 221 Integer.class); 222 verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), 223 requestIdCaptor.capture(), nullable(Notification.class), eq(userHandle)); 224 verify(mNotificationManager).cancelAsUser(nullable(String.class), 225 eq(requestIdCaptor.getValue()), eq(userHandle)); 226 227 // Verify that the second call to showMissedCallNotification behaves like it were the first. 228 verify(builder2).setContentText(CALLER_NAME); 229 } 230 231 @SmallTest 232 @Test 233 public void testNotifyMultipleMissedCalls() { 234 Notification.Builder[] builders = new Notification.Builder[4]; 235 236 for (int i = 0; i < 4; i++) { 237 builders[i] = makeNotificationBuilder("builder" + Integer.toString(i)); 238 } 239 240 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 241 MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, 242 CALL_TIMESTAMP, phoneAccount.getAccountHandle()); 243 244 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 245 makeNotificationBuilderFactory(builders); 246 247 MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, 248 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory); 249 250 missedCallNotifier.showMissedCallNotification(fakeCall); 251 missedCallNotifier.showMissedCallNotification(fakeCall); 252 253 // The following captor is to capture the two notifications that got passed into 254 // notifyAsUser. This distinguishes between the builders used for the full notification 255 // (i.e. the one potentially containing sensitive information, such as phone numbers), 256 // and the builders used for the notifications shown on the lockscreen, which have been 257 // scrubbed of such sensitive info. The notifications which are used as arguments 258 // to notifyAsUser are the versions which contain sensitive information. 259 ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass( 260 Notification.class); 261 verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), eq(1), 262 notificationArgumentCaptor.capture(), eq(PRIMARY_USER)); 263 HashSet<String> privateNotifications = new HashSet<>(); 264 for (Notification n : notificationArgumentCaptor.getAllValues()) { 265 privateNotifications.add(n.toString()); 266 } 267 268 for (int i = 0; i < 4; i++) { 269 Notification.Builder builder = builders[i]; 270 verify(builder).setWhen(CALL_TIMESTAMP); 271 if (i >= 2) { 272 // The builders after the first two are for multiple missed calls. The notification 273 // for subsequent missed calls is expected to be different in terms of the text 274 // contents of the notification, and that is verified here. 275 if (privateNotifications.contains(builder.toString())) { 276 verify(builder).setContentText(String.format(MISSED_CALLS_MSG, 2)); 277 verify(builder).setContentTitle(MISSED_CALLS_TITLE); 278 } else { 279 verify(builder).setContentText(MISSED_CALLS_TITLE); 280 verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL); 281 } 282 verify(builder, never()).addAction(any(Notification.Action.class)); 283 } else { 284 if (privateNotifications.contains(builder.toString())) { 285 verify(builder).setContentText(CALLER_NAME); 286 verify(builder).setContentTitle(MISSED_CALL_TITLE); 287 } else { 288 verify(builder).setContentText(MISSED_CALL_TITLE); 289 verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL); 290 } 291 } 292 } 293 } 294 295 @SmallTest 296 @Test 297 public void testNotifySingleCallInPrimaryUser() { 298 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 299 notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); 300 } 301 302 @SmallTest 303 @Test 304 public void testNotifySingleCallInSecondaryUser() { 305 PhoneAccount phoneAccount = makePhoneAccount(SECONARY_USER, NO_CAPABILITY); 306 notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); 307 } 308 309 @SmallTest 310 @Test 311 public void testNotifySingleCallInSecondaryUserWithMultiUserCapability() { 312 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, 313 PhoneAccount.CAPABILITY_MULTI_USER); 314 notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); 315 } 316 317 @SmallTest 318 @Test 319 public void testNotifySingleCallWhenCurrentUserIsSecondaryUser() { 320 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 321 notifySingleCallTestInternal(phoneAccount, SECONARY_USER); 322 } 323 324 @SmallTest 325 @Test 326 public void testNotifySingleCall() { 327 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 328 notifySingleCallTestInternal(phoneAccount, PRIMARY_USER); 329 } 330 331 private void notifySingleCallTestInternal(PhoneAccount phoneAccount, UserHandle currentUser) { 332 Notification.Builder builder1 = makeNotificationBuilder("builder1"); 333 Notification.Builder builder2 = makeNotificationBuilder("builder2"); 334 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 335 makeNotificationBuilderFactory(builder1, builder2); 336 337 MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory, 338 currentUser); 339 340 MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME, 341 CALL_TIMESTAMP, phoneAccount.getAccountHandle()); 342 missedCallNotifier.showMissedCallNotification(fakeCall); 343 344 ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass( 345 Notification.class); 346 347 UserHandle expectedUserHandle; 348 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 349 expectedUserHandle = currentUser; 350 } else { 351 expectedUserHandle = phoneAccount.getAccountHandle().getUserHandle(); 352 } 353 verify(mNotificationManager).notifyAsUser(nullable(String.class), eq(1), 354 notificationArgumentCaptor.capture(), eq((expectedUserHandle))); 355 356 Notification.Builder builder; 357 Notification.Builder publicBuilder; 358 359 if (notificationArgumentCaptor.getValue().toString().equals("builder1")) { 360 builder = builder1; 361 publicBuilder = builder2; 362 } else { 363 builder = builder2; 364 publicBuilder = builder1; 365 } 366 367 verify(builder).setWhen(CALL_TIMESTAMP); 368 verify(publicBuilder).setWhen(CALL_TIMESTAMP); 369 370 verify(builder).setContentText(CALLER_NAME); 371 verify(publicBuilder).setContentText(MISSED_CALL_TITLE); 372 373 verify(builder).setContentTitle(MISSED_CALL_TITLE); 374 verify(publicBuilder).setContentTitle(USER_CALL_ACTIVITY_LABEL); 375 376 // Create two intents that correspond to call-back and respond back with SMS, and assert 377 // that these pending intents have in fact been registered. 378 Intent callBackIntent = new Intent( 379 TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, 380 TEL_CALL_HANDLE, 381 mContext, 382 TelecomBroadcastReceiver.class); 383 Intent smsIntent = new Intent( 384 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, 385 Uri.fromParts(Constants.SCHEME_SMSTO, TEL_CALL_HANDLE.getSchemeSpecificPart(), null), 386 mContext, 387 TelecomBroadcastReceiver.class); 388 389 assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, 390 callBackIntent, PendingIntent.FLAG_NO_CREATE)); 391 assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, 392 smsIntent, PendingIntent.FLAG_NO_CREATE)); 393 } 394 395 @SmallTest 396 @Test 397 public void testNoSmsBackAfterMissedSipCall() { 398 Notification.Builder builder1 = makeNotificationBuilder("builder1"); 399 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 400 makeNotificationBuilderFactory(builder1); 401 402 MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, 403 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory); 404 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 405 406 MissedCallNotifier.CallInfo fakeCall = 407 makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP, 408 phoneAccount.getAccountHandle()); 409 missedCallNotifier.showMissedCallNotification(fakeCall); 410 411 // Create two intents that correspond to call-back and respond back with SMS, and assert 412 // that in the case of a SIP call, no SMS intent is generated. 413 Intent callBackIntent = new Intent( 414 TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, 415 SIP_CALL_HANDLE, 416 mContext, 417 TelecomBroadcastReceiver.class); 418 Intent smsIntent = new Intent( 419 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, 420 Uri.fromParts(Constants.SCHEME_SMSTO, SIP_CALL_HANDLE.getSchemeSpecificPart(), 421 null), 422 mContext, 423 TelecomBroadcastReceiver.class); 424 425 assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, 426 callBackIntent, PendingIntent.FLAG_NO_CREATE)); 427 assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID, 428 smsIntent, PendingIntent.FLAG_NO_CREATE)); 429 } 430 431 @SmallTest 432 @Test 433 public void testLoadOneCallFromDb() throws Exception { 434 CallerInfoLookupHelper mockCallerInfoLookupHelper = mock(CallerInfoLookupHelper.class); 435 MissedCallNotifier.CallInfoFactory mockCallInfoFactory = 436 mock(MissedCallNotifier.CallInfoFactory.class); 437 438 Uri queryUri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, 439 PRIMARY_USER.getIdentifier()); 440 IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier()); 441 442 Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder() 443 .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(), 444 CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) 445 .build(); 446 447 when(cp.query(anyString(), eq(queryUri), nullable(String[].class), 448 nullable(Bundle.class), nullable(ICancellationSignal.class))) 449 .thenReturn(mockMissedCallsCursor); 450 451 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 452 MissedCallNotifier.CallInfo fakeCallInfo = makeFakeCallInfo(TEL_CALL_HANDLE, 453 CALLER_NAME, CALL_TIMESTAMP, phoneAccount.getAccountHandle()); 454 when(mockCallInfoFactory.makeCallInfo(nullable(CallerInfo.class), 455 nullable(PhoneAccountHandle.class), nullable(Uri.class), eq(CALL_TIMESTAMP))) 456 .thenReturn(fakeCallInfo); 457 458 Notification.Builder builder1 = makeNotificationBuilder("builder1"); 459 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 460 makeNotificationBuilderFactory(builder1); 461 462 MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, 463 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory); 464 465 // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below 466 // timeout-verify, so run this in a new handler to mitigate that. 467 Handler h = new Handler(Looper.getMainLooper()); 468 h.post(() -> missedCallNotifier.reloadFromDatabase( 469 mockCallerInfoLookupHelper, mockCallInfoFactory, PRIMARY_USER)); 470 waitForHandlerAction(h, TEST_TIMEOUT); 471 472 // TelecomSystem.getInstance returns null in this test, so we expect that nothing will 473 // happen. 474 verify(mockCallerInfoLookupHelper, never()).startLookup(any(Uri.class), 475 any(CallerInfoLookupHelper.OnQueryCompleteListener.class)); 476 // Simulate a boot-complete 477 TelecomSystem.setInstance(mTelecomSystem); 478 when(mTelecomSystem.isBootComplete()).thenReturn(true); 479 h.post(() -> missedCallNotifier.reloadAfterBootComplete(mockCallerInfoLookupHelper, 480 mockCallInfoFactory)); 481 waitForHandlerAction(h, TEST_TIMEOUT); 482 483 Uri escapedHandle = Uri.fromParts(PhoneAccount.SCHEME_TEL, 484 TEL_CALL_HANDLE.getSchemeSpecificPart(), null); 485 ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor = 486 ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class); 487 verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedHandle), 488 listenerCaptor.capture()); 489 490 CallerInfo ci = new CallerInfo(); 491 listenerCaptor.getValue().onCallerInfoQueryComplete(escapedHandle, ci); 492 verify(mockCallInfoFactory).makeCallInfo(eq(ci), isNull(PhoneAccountHandle.class), 493 eq(escapedHandle), eq(CALL_TIMESTAMP)); 494 } 495 496 @SmallTest 497 @Test 498 public void testLoadTwoCallsFromDb() throws Exception { 499 TelecomSystem.setInstance(mTelecomSystem); 500 when(mTelecomSystem.isBootComplete()).thenReturn(true); 501 CallerInfoLookupHelper mockCallerInfoLookupHelper = mock(CallerInfoLookupHelper.class); 502 MissedCallNotifier.CallInfoFactory mockCallInfoFactory = 503 mock(MissedCallNotifier.CallInfoFactory.class); 504 505 Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder() 506 .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(), 507 CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) 508 .addEntry(SIP_CALL_HANDLE.getSchemeSpecificPart(), 509 CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP) 510 .build(); 511 512 Uri queryUri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, 513 PRIMARY_USER.getIdentifier()); 514 IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier()); 515 516 when(cp.query(anyString(), eq(queryUri), nullable(String[].class), 517 nullable(Bundle.class), nullable(ICancellationSignal.class))) 518 .thenReturn(mockMissedCallsCursor); 519 520 PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY); 521 MissedCallNotifier.CallInfo fakeCallInfo = makeFakeCallInfo(TEL_CALL_HANDLE, 522 CALLER_NAME, CALL_TIMESTAMP, phoneAccount.getAccountHandle()); 523 when(mockCallInfoFactory.makeCallInfo(nullable(CallerInfo.class), 524 nullable(PhoneAccountHandle.class), nullable(Uri.class), eq(CALL_TIMESTAMP))) 525 .thenReturn(fakeCallInfo); 526 527 Notification.Builder builder1 = makeNotificationBuilder("builder1"); 528 MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory = 529 makeNotificationBuilderFactory(builder1); 530 531 MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, 532 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory); 533 534 // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below 535 // timeout-verify, so run this in a new handler to mitigate that. 536 Handler h = new Handler(Looper.getMainLooper()); 537 h.post(() -> missedCallNotifier.reloadFromDatabase( 538 mockCallerInfoLookupHelper, mockCallInfoFactory, PRIMARY_USER)); 539 waitForHandlerAction(h, TEST_TIMEOUT); 540 541 Uri escapedTelHandle = Uri.fromParts(PhoneAccount.SCHEME_TEL, 542 TEL_CALL_HANDLE.getSchemeSpecificPart(), null); 543 Uri escapedSipHandle = Uri.fromParts(PhoneAccount.SCHEME_SIP, 544 SIP_CALL_HANDLE.getSchemeSpecificPart(), null); 545 546 ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor = 547 ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class); 548 verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedTelHandle), 549 listenerCaptor.capture()); 550 verify(mockCallerInfoLookupHelper, timeout(TEST_TIMEOUT)).startLookup(eq(escapedSipHandle), 551 listenerCaptor.capture()); 552 553 CallerInfo ci = new CallerInfo(); 554 listenerCaptor.getAllValues().get(0).onCallerInfoQueryComplete(escapedTelHandle, ci); 555 listenerCaptor.getAllValues().get(1).onCallerInfoQueryComplete(escapedSipHandle, ci); 556 557 // Verify that two notifications were generated, both with the same id. 558 verify(mNotificationManager, times(2)).notifyAsUser(nullable(String.class), eq(1), 559 nullable(Notification.class), eq(PRIMARY_USER)); 560 } 561 562 private Notification.Builder makeNotificationBuilder(String label) { 563 Notification.Builder builder = spy(new Notification.Builder(mContext)); 564 Notification notification = mock(Notification.class); 565 when(notification.toString()).thenReturn(label); 566 when(builder.toString()).thenReturn(label); 567 doReturn(notification).when(builder).build(); 568 return builder; 569 } 570 571 private MissedCallNotifier.CallInfo makeFakeCallInfo(Uri handle, String name, long timestamp, 572 PhoneAccountHandle phoneAccountHandle) { 573 MissedCallNotifier.CallInfo fakeCall = mock(MissedCallNotifier.CallInfo.class); 574 when(fakeCall.getHandle()).thenReturn(handle); 575 when(fakeCall.getHandleSchemeSpecificPart()).thenReturn(handle.getSchemeSpecificPart()); 576 when(fakeCall.getName()).thenReturn(name); 577 when(fakeCall.getCreationTimeMillis()).thenReturn(timestamp); 578 when(fakeCall.getPhoneAccountHandle()).thenReturn(phoneAccountHandle); 579 return fakeCall; 580 } 581 582 private MissedCallNotifierImpl.NotificationBuilderFactory makeNotificationBuilderFactory( 583 Notification.Builder... builders) { 584 MissedCallNotifierImpl.NotificationBuilderFactory builderFactory = 585 mock(MissedCallNotifierImpl.NotificationBuilderFactory.class); 586 when(builderFactory.getBuilder(mContext)).thenReturn(builders[0], 587 Arrays.copyOfRange(builders, 1, builders.length)); 588 return builderFactory; 589 } 590 591 private MissedCallNotifier makeMissedCallNotifier( 592 NotificationBuilderFactory fakeBuilderFactory, UserHandle currentUser) { 593 MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext, 594 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory); 595 missedCallNotifier.setCurrentUserHandle(currentUser); 596 return missedCallNotifier; 597 } 598 599 private PhoneAccount makePhoneAccount(UserHandle userHandle, int capability) { 600 ComponentName componentName = new ComponentName("com.anything", "com.whatever"); 601 PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(componentName, "id", 602 userHandle); 603 PhoneAccount.Builder builder = new PhoneAccount.Builder(phoneAccountHandle, "test"); 604 builder.setCapabilities(capability); 605 PhoneAccount phoneAccount = builder.build(); 606 when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle)) 607 .thenReturn(phoneAccount); 608 return phoneAccount; 609 } 610 611 private IContentProvider getContentProviderForUser(int userId) { 612 return mContext.getContentResolver().acquireProvider(userId + "@call_log"); 613 } 614 615} 616