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