1 /*
2  * Copyright (C) 2011 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.dialer.calllog;
18 
19 import com.google.common.collect.Lists;
20 
21 import android.content.ContentUris;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.MatrixCursor;
26 import android.net.Uri;
27 import android.provider.CallLog.Calls;
28 import android.provider.ContactsContract.CommonDataKinds.Phone;
29 import android.provider.VoicemailContract;
30 import android.telephony.PhoneNumberUtils;
31 import android.test.AndroidTestCase;
32 import android.test.suitebuilder.annotation.MediumTest;
33 import android.text.TextUtils;
34 import android.view.View;
35 
36 import com.android.contacts.common.compat.CompatUtils;
37 import com.android.contacts.common.preference.ContactsPreferences;
38 import com.android.dialer.contactinfo.ContactInfoCache;
39 import com.android.dialer.database.VoicemailArchiveContract;
40 import com.android.dialer.util.AppCompatConstants;
41 import com.android.dialer.util.TestConstants;
42 import com.android.dialer.R;
43 
44 import java.util.Date;
45 import java.util.List;
46 import java.util.Random;
47 
48 /**
49  * Unit tests for {@link CallLogAdapter}.
50  *
51  * adb shell am instrument \
52  *     -e com.android.dialer.calllog.CallLogAdapterTest \
53  *     -w com.android.dialer.tests/android.test.InstrumentationTestRunner
54  */
55 public class CallLogAdapterTest extends AndroidTestCase {
56     private static final String EMPTY_STRING = "";
57     private static final int NO_VALUE_SET = -1;
58     private static final int ARCHIVE_TYPE = -2;
59 
60     private static final String TEST_CACHED_NAME_PRIMARY = "Cached Name";
61     private static final String TEST_CACHED_NAME_ALTERNATIVE = "Name Cached";
62     private static final String CONTACT_NAME_PRIMARY = "Contact Name";
63     private static final String CONTACT_NAME_ALTERNATIVE = "Name, Contact";
64     private static final String TEST_CACHED_NUMBER_LABEL = "label";
65     private static final int TEST_CACHED_NUMBER_TYPE = 1;
66     private static final String TEST_COUNTRY_ISO = "US";
67     private static final String TEST_DEFAULT_CUSTOM_LABEL = "myLabel";
68     private static final Uri TEST_LOOKUP_URI = Uri.parse("content://contacts/2");
69     private static final String TEST_ACCOUNT_ID_LABEL = "label";
70 
71     private static final String TEST_NUMBER = "12125551000";
72     private static final String TEST_NUMBER_1 = "12345678";
73     private static final String TEST_NUMBER_2 = "87654321";
74     private static final String TEST_NUMBER_3 = "18273645";
75     private static final String TEST_POST_DIAL_DIGITS = ";12345";
76     private static final String TEST_VIA_NUMBER = "+16505551234";
77     private static final String TEST_FORMATTED_NUMBER = "1 212-555-1000";
78 
79     // The object under test.
80     private TestCallLogAdapter mAdapter;
81 
82     private MatrixCursor mCursor;
83     private Resources mResources;
84 
85     private CallLogListItemViewHolder mViewHolder;
86     private final Random mRandom = new Random();
87 
88     @Override
setUp()89     protected void setUp() throws Exception {
90         super.setUp();
91         mContext = getContext();
92         mResources = mContext.getResources();
93 
94         // Use a call fetcher that does not do anything.
95         CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
96             @Override
97             public void fetchCalls() {}
98         };
99 
100         ContactInfoHelper fakeContactInfoHelper =
101                 new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
102                     @Override
103                     public ContactInfo lookupNumber(String number, String countryIso) {
104                         ContactInfo info = new ContactInfo();
105                         info.number = number;
106                         info.formattedNumber = number;
107                         return info;
108                     }
109                 };
110 
111         mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper,
112                 CallLogAdapter.ACTIVITY_TYPE_DIALTACTS);
113 
114         // The cursor used in the tests to store the entries to display.
115         mCursor = new MatrixCursor(CallLogQuery._PROJECTION);
116         mCursor.moveToFirst();
117 
118         // The views into which to store the data.
119         mViewHolder = CallLogListItemViewHolder.createForTest(getContext());
120     }
121 
122     @MediumTest
testBindView_NumberOnlyNoCache()123     public void testBindView_NumberOnlyNoCache() {
124         createCallLogEntry();
125 
126         mAdapter.changeCursor(mCursor);
127         mAdapter.onBindViewHolder(mViewHolder, 0);
128 
129         assertNameIs(mViewHolder, TEST_NUMBER);
130     }
131 
132     @MediumTest
testBindView_PrivateCall()133     public void testBindView_PrivateCall() {
134         createPrivateCallLogEntry();
135 
136         mAdapter.changeCursor(mCursor);
137         mAdapter.onBindViewHolder(mViewHolder, 0);
138 
139         assertEquals(Calls.PRESENTATION_RESTRICTED, mViewHolder.numberPresentation);
140         assertNull(mViewHolder.primaryActionButtonView.getTag());
141         // QC should be disabled since there are no actions to be performed on this
142         // call.
143         assertFalse(mViewHolder.quickContactView.isEnabled());
144     }
145 
146     @MediumTest
testBindView_UnknownCall()147     public void testBindView_UnknownCall() {
148         createUnknownCallLogEntry();
149 
150         mAdapter.changeCursor(mCursor);
151         mAdapter.onBindViewHolder(mViewHolder, 0);
152 
153         assertEquals(Calls.PRESENTATION_UNKNOWN, mViewHolder.numberPresentation);
154         assertNull(mViewHolder.primaryActionButtonView.getTag());
155         // QC should be disabled since there are no actions to be performed on this
156         // call.
157         assertFalse(mViewHolder.quickContactView.isEnabled());
158     }
159 
160     @MediumTest
testBindView_WithoutQuickContactBadge()161     public void testBindView_WithoutQuickContactBadge() {
162         createCallLogEntry();
163 
164         mAdapter.changeCursor(mCursor);
165         mAdapter.onBindViewHolder(mViewHolder, 0);
166 
167         //assertFalse(mViewHolder.quickContactView.isEnabled());
168     }
169 
170     @MediumTest
testBindView_CallButton()171     public void testBindView_CallButton() {
172         createCallLogEntry();
173 
174         mAdapter.changeCursor(mCursor);
175         mAdapter.onBindViewHolder(mViewHolder, 0);
176 
177         // The primaryActionView tag is set when the ViewHolder is binded. If it is possible
178         // to place a call to the phone number, a call intent will have been created which
179         // starts a phone call to the entry's number.
180         assertHasCallAction(mViewHolder);
181     }
182 
183     @MediumTest
testBindView_FirstNameFirstOrder()184     public void testBindView_FirstNameFirstOrder() {
185         createCallLogEntry();
186 
187         mAdapter.getContactInfoCache()
188                 .mockGetValue(createContactInfo(CONTACT_NAME_PRIMARY, CONTACT_NAME_ALTERNATIVE));
189 
190         setNameDisplayOrder(getContext(), ContactsPreferences.DISPLAY_ORDER_PRIMARY);
191 
192         mAdapter.changeCursor(mCursor);
193         mAdapter.onBindViewHolder(mViewHolder, 0);
194         assertEquals(CONTACT_NAME_PRIMARY, mViewHolder.phoneCallDetailsViews.nameView.getText());
195     }
196 
197     @MediumTest
testBindView_LastNameFirstOrder()198     public void testBindView_LastNameFirstOrder() {
199         createCallLogEntry();
200 
201         mAdapter.getContactInfoCache()
202                 .mockGetValue(createContactInfo(CONTACT_NAME_PRIMARY, CONTACT_NAME_ALTERNATIVE));
203 
204         setNameDisplayOrder(getContext(), ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE);
205 
206         mAdapter.changeCursor(mCursor);
207         mAdapter.onBindViewHolder(mViewHolder, 0);
208         assertEquals(CONTACT_NAME_ALTERNATIVE,
209                 mViewHolder.phoneCallDetailsViews.nameView.getText());
210     }
211 
212     @MediumTest
testBindView_NameOrderCorrectOnChange()213     public void testBindView_NameOrderCorrectOnChange() {
214         createCallLogEntry();
215 
216         mAdapter.getContactInfoCache()
217                 .mockGetValue(createContactInfo(CONTACT_NAME_PRIMARY, CONTACT_NAME_ALTERNATIVE));
218 
219         Context context = getContext();
220         setNameDisplayOrder(context, ContactsPreferences.DISPLAY_ORDER_PRIMARY);
221 
222         mAdapter.changeCursor(mCursor);
223         mAdapter.onBindViewHolder(mViewHolder, 0);
224         assertEquals(CONTACT_NAME_PRIMARY,
225                 mViewHolder.phoneCallDetailsViews.nameView.getText());
226 
227         setNameDisplayOrder(context, ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE);
228         mAdapter.onResume();
229 
230         mAdapter.onBindViewHolder(mViewHolder, 0);
231         assertEquals(CONTACT_NAME_ALTERNATIVE,
232                 mViewHolder.phoneCallDetailsViews.nameView.getText());
233     }
234 
setNameDisplayOrder(Context context, int displayOrder)235     private void setNameDisplayOrder(Context context, int displayOrder) {
236         context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE).edit().putInt(
237                 ContactsPreferences.DISPLAY_ORDER_KEY, displayOrder).commit();
238     }
239 
240     @MediumTest
testBindView_CallButtonWithPostDialDigits()241     public void testBindView_CallButtonWithPostDialDigits() {
242         createCallLogEntry(TEST_NUMBER, TEST_POST_DIAL_DIGITS, NO_VALUE_SET, NO_VALUE_SET);
243 
244         mAdapter.changeCursor(mCursor);
245         mAdapter.onBindViewHolder(mViewHolder, 0);
246 
247         if (CompatUtils.isNCompatible()) {
248             assertHasCallActionToGivenNumber(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
249         }
250     }
251 
252     @MediumTest
testBindView_VoicemailUri()253     public void testBindView_VoicemailUri() {
254         createVoicemailCallLogEntry();
255 
256         mAdapter.changeCursor(mCursor);
257         mAdapter.onBindViewHolder(mViewHolder, 0);
258 
259         assertEquals(Uri.parse(mViewHolder.voicemailUri),
260                 ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, 0));
261         assertNull(mViewHolder.primaryActionButtonView.getTag());
262     }
263 
264     @MediumTest
testBindView_NumberWithPostDialDigits()265     public void testBindView_NumberWithPostDialDigits() {
266         createCallLogEntry(TEST_NUMBER, TEST_POST_DIAL_DIGITS, NO_VALUE_SET, NO_VALUE_SET);
267 
268         mAdapter.changeCursor(mCursor);
269         mAdapter.onBindViewHolder(mViewHolder, 0);
270 
271         if (CompatUtils.isNCompatible()) {
272             assertNameIs(mViewHolder, TEST_NUMBER + TEST_POST_DIAL_DIGITS);
273         }
274     }
275 
276     @MediumTest
testBindView_ContactWithPostDialDigits()277     public void testBindView_ContactWithPostDialDigits() {
278         createCallLogEntry(TEST_NUMBER, TEST_POST_DIAL_DIGITS, NO_VALUE_SET, NO_VALUE_SET);
279         mAdapter.injectContactInfoForTest(TEST_NUMBER + TEST_POST_DIAL_DIGITS, TEST_COUNTRY_ISO,
280                 createContactInfo());
281 
282         mAdapter.changeCursor(mCursor);
283         mAdapter.onBindViewHolder(mViewHolder, 0);
284 
285         if (CompatUtils.isNCompatible()) {
286             assertNameIs(mViewHolder, TEST_CACHED_NAME_PRIMARY);
287         }
288     }
289 
290     @MediumTest
testBindView_CallLogWithViaNumber()291     public void testBindView_CallLogWithViaNumber() {
292         createCallLogEntry(TEST_NUMBER, EMPTY_STRING, TEST_VIA_NUMBER, NO_VALUE_SET, NO_VALUE_SET);
293 
294         mAdapter.changeCursor(mCursor);
295         mAdapter.onBindViewHolder(mViewHolder, 0);
296 
297         // Copy format of Resource String
298         String formattedNumber = mResources.getString(R.string.description_via_number,
299                 TEST_VIA_NUMBER);
300 
301         if (CompatUtils.isNCompatible()) {
302             assertEquals(formattedNumber,
303                     mViewHolder.phoneCallDetailsViews.callAccountLabel.getText());
304         }
305     }
306 
307     @MediumTest
testBindView_CallLogWithoutViaNumber()308     public void testBindView_CallLogWithoutViaNumber() {
309         createCallLogEntry(TEST_NUMBER, EMPTY_STRING, EMPTY_STRING, NO_VALUE_SET, NO_VALUE_SET);
310 
311         mAdapter.changeCursor(mCursor);
312         mAdapter.onBindViewHolder(mViewHolder, 0);
313 
314         if (CompatUtils.isNCompatible()) {
315             assertEquals(View.GONE,
316                     mViewHolder.phoneCallDetailsViews.callAccountLabel.getVisibility());
317         }
318     }
319 
320     @MediumTest
testPresentationAfterRebindingViewHolders()321     public void testPresentationAfterRebindingViewHolders() {
322         final int increment = 10;
323         final int size = increment * 4;
324 
325         // Instantiate list of ViewHolders.
326         CallLogListItemViewHolder[] holders = new CallLogListItemViewHolder[size];
327         for (int i = 0; i < size; i++) {
328             holders[i] = CallLogListItemViewHolder.createForTest(getContext());
329         }
330 
331         // Add first set of entries to the cursor.
332         for (int i = 0; i < increment; i++) {
333             createCallLogEntry();
334             createPrivateCallLogEntry();
335             createCallLogEntry();
336             createUnknownCallLogEntry();
337         }
338 
339         mAdapter.changeCursor(mCursor);
340 
341         // Verify correct appearance for presentation.
342         for (int i = 0; i < size; i++) {
343             mAdapter.onBindViewHolder(holders[i], i);
344             if (holders[i].numberPresentation == Calls.PRESENTATION_ALLOWED) {
345                 assertHasCallAction(holders[i]);
346             } else {
347                 assertNull(holders[i].primaryActionButtonView.getTag());
348                 assertEquals(holders[i].number, EMPTY_STRING);
349             }
350         }
351 
352         // Append the rest of the entries to the cursor. Keep the first set of ViewHolders
353         // so they are updated and not buitl from scratch. This checks for bugs which may
354         // be evident only after the call log is updated.
355         for (int i = 0; i < increment; i++) {
356             createPrivateCallLogEntry();
357             createCallLogEntry();
358             createUnknownCallLogEntry();
359             createCallLogEntry();
360         }
361 
362         mCursor.move(size);
363 
364         // Verify correct appearnce for presentation.
365         for (int i = 0; i < size; i++) {
366             mAdapter.onBindViewHolder(holders[i], i + size);
367             if (holders[i].numberPresentation == Calls.PRESENTATION_ALLOWED) {
368                 assertHasCallAction(holders[i]);
369             } else {
370                 assertNull(holders[i].primaryActionButtonView.getTag());
371                 assertEquals(holders[i].number, EMPTY_STRING);
372             }
373         }
374     }
375 
376    @MediumTest
testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest()377    public void testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest() {
378        createCallLogEntry();
379 
380        // Bind the views of a single row.
381        mAdapter.changeCursor(mCursor);
382        mAdapter.onBindViewHolder(mViewHolder, 0);
383 
384        // There is one request for contact details.
385        assertEquals(1, mAdapter.getContactInfoCache().requests.size());
386 
387        TestContactInfoCache.Request request = mAdapter.getContactInfoCache().requests.get(0);
388        // It is for the number we need to show.
389        assertEquals(TEST_NUMBER, request.number);
390        // It has the right country.
391        assertEquals(TEST_COUNTRY_ISO, request.countryIso);
392        // Since there is nothing in the cache, it is an immediate request.
393        assertTrue("should be immediate", request.immediate);
394    }
395 
396    @MediumTest
testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest()397    public void testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest() {
398        createCallLogEntryWithCachedValues(false);
399 
400        // Bind the views of a single row.
401        mAdapter.changeCursor(mCursor);
402        mAdapter.onBindViewHolder(mViewHolder, 0);
403 
404         // There is one request for contact details.
405         assertEquals(1, mAdapter.getContactInfoCache().requests.size());
406 
407         TestContactInfoCache.Request request = mAdapter.getContactInfoCache().requests.get(0);
408 
409         // The values passed to the request, match the ones in the call log cache.
410         assertEquals(TEST_CACHED_NAME_PRIMARY, request.callLogInfo.name);
411         assertEquals(TEST_CACHED_NUMBER_TYPE, request.callLogInfo.type);
412         assertEquals(TEST_CACHED_NUMBER_LABEL, request.callLogInfo.label);
413     }
414 
415     @MediumTest
testBindView_NoCallLogButMemoryCache_EnqueueRequest()416     public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
417         createCallLogEntry();
418         mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
419 
420         // Bind the views of a single row.
421         mAdapter.changeCursor(mCursor);
422         mAdapter.onBindViewHolder(mViewHolder, 0);
423 
424         // There is one request for contact details.
425         assertEquals(1, mAdapter.getContactInfoCache().requests.size());
426 
427         TestContactInfoCache.Request request = mAdapter.getContactInfoCache().requests.get(0);
428         // Since there is something in the cache, it is not an immediate request.
429         assertFalse("should not be immediate", request.immediate);
430     }
431 
432     @MediumTest
testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest()433     public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
434         createCallLogEntryWithCachedValues(true);
435 
436         // Bind the views of a single row.
437         mAdapter.changeCursor(mCursor);
438         mAdapter.onBindViewHolder(mViewHolder, 0);
439 
440         // Cache and call log are up-to-date: no need to request update.
441         assertEquals(0, mAdapter.getContactInfoCache().requests.size());
442     }
443 
444     @MediumTest
testBindView_MismatchBetweenCallLogAndMemoryCache_EnqueueRequest()445     public void testBindView_MismatchBetweenCallLogAndMemoryCache_EnqueueRequest() {
446         createCallLogEntryWithCachedValues(false);
447 
448         // Contact info contains a different name.
449         ContactInfo info = createContactInfo();
450         info.name = "new name";
451         mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, info);
452 
453         // Bind the views of a single row.
454         mAdapter.changeCursor(mCursor);
455         mAdapter.onBindViewHolder(mViewHolder, 0);
456 
457         // There is one request for contact details.
458         assertEquals(1, mAdapter.getContactInfoCache().requests.size());
459 
460         TestContactInfoCache.Request request = mAdapter.getContactInfoCache().requests.get(0);
461         // Since there is something in the cache, it is not an immediate request.
462         assertFalse("should not be immediate", request.immediate);
463     }
464 
465     @MediumTest
testBindView_WithCachedName()466     public void testBindView_WithCachedName() {
467         createCallLogEntryWithCachedValues(
468                 "John Doe",
469                 Phone.TYPE_HOME,
470                 TEST_CACHED_NUMBER_LABEL);
471 
472         mAdapter.changeCursor(mCursor);
473         mAdapter.onBindViewHolder(mViewHolder, 0);
474 
475         assertNameIs(mViewHolder, "John Doe");
476         assertLabel(mViewHolder, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
477     }
478 
479     @MediumTest
testBindView_UriNumber()480     public void testBindView_UriNumber() {
481         createCallLogEntryWithCachedValues(
482                 "sip:johndoe@gmail.com",
483                 AppCompatConstants.CALLS_INCOMING_TYPE,
484                 "John Doe",
485                 Phone.TYPE_HOME,
486                 TEST_DEFAULT_CUSTOM_LABEL,
487                 EMPTY_STRING,
488                 false /* inject */);
489 
490         mAdapter.changeCursor(mCursor);
491         mAdapter.onBindViewHolder(mViewHolder, 0);
492 
493         assertNameIs(mViewHolder, "John Doe");
494         assertLabel(mViewHolder, "sip:johndoe@gmail.com", "sip:johndoe@gmail.com");
495     }
496 
497     @MediumTest
testBindView_HomeLabel()498     public void testBindView_HomeLabel() {
499         createCallLogEntryWithCachedValues(
500                 "John Doe",
501                 Phone.TYPE_HOME,
502                 TEST_CACHED_NUMBER_LABEL);
503 
504         mAdapter.changeCursor(mCursor);
505         mAdapter.onBindViewHolder(mViewHolder, 0);
506 
507         assertNameIs(mViewHolder, "John Doe");
508         assertLabel(mViewHolder, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
509     }
510 
511     @MediumTest
testBindView_WorkLabel()512     public void testBindView_WorkLabel() {
513         createCallLogEntryWithCachedValues(
514                 "John Doe",
515                 Phone.TYPE_WORK,
516                 TEST_CACHED_NUMBER_LABEL);
517 
518         mAdapter.changeCursor(mCursor);
519         mAdapter.onBindViewHolder(mViewHolder, 0);
520 
521         assertNameIs(mViewHolder, "John Doe");
522         assertLabel(mViewHolder, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_WORK));
523     }
524 
525     @MediumTest
testBindView_CustomLabel()526     public void testBindView_CustomLabel() {
527         createCallLogEntryWithCachedValues(
528                 "John Doe",
529                 Phone.TYPE_CUSTOM,
530                 TEST_DEFAULT_CUSTOM_LABEL);
531 
532         mAdapter.changeCursor(mCursor);
533         mAdapter.onBindViewHolder(mViewHolder, 0);
534 
535         assertNameIs(mViewHolder, "John Doe");
536         assertLabel(mViewHolder, TEST_FORMATTED_NUMBER, TEST_DEFAULT_CUSTOM_LABEL);
537     }
538 
539     @MediumTest
testBindView_NumberOnlyDbCachedFormattedNumber()540     public void testBindView_NumberOnlyDbCachedFormattedNumber() {
541         createCallLogEntryWithCachedValues(
542                 TEST_NUMBER,
543                 AppCompatConstants.CALLS_INCOMING_TYPE,
544                 EMPTY_STRING,
545                 TEST_CACHED_NUMBER_TYPE,
546                 TEST_CACHED_NUMBER_LABEL,
547                 TEST_FORMATTED_NUMBER,
548                 false /* inject */);
549 
550         mAdapter.changeCursor(mCursor);
551         mAdapter.onBindViewHolder(mViewHolder, 0);
552 
553         assertNameIs(mViewHolder, TEST_FORMATTED_NUMBER);
554     }
555 
556     @MediumTest
testBindVoicemailPromoCard()557     public void testBindVoicemailPromoCard() {
558         createCallLogEntry(TEST_NUMBER_1);
559         createCallLogEntry(TEST_NUMBER_1);
560         createCallLogEntry(TEST_NUMBER_2);
561         createCallLogEntry(TEST_NUMBER_2);
562         createCallLogEntry(TEST_NUMBER_2);
563         createCallLogEntry(TEST_NUMBER_3);
564 
565         // Bind the voicemail promo card.
566         mAdapter.showVoicemailPromoCard(true);
567         mAdapter.changeCursor(mCursor);
568         mAdapter.onBindViewHolder(PromoCardViewHolder.createForTest(getContext()), 0);
569 
570         // Check that displaying the promo card does not affect the grouping or list display.
571         mAdapter.onBindViewHolder(mViewHolder, 1);
572         assertEquals(2, mAdapter.getGroupSize(1));
573         assertEquals(TEST_NUMBER_1, mViewHolder.number);
574 
575         mAdapter.onBindViewHolder(mViewHolder, 2);
576         assertEquals(3, mAdapter.getGroupSize(2));
577         assertEquals(TEST_NUMBER_2, mViewHolder.number);
578 
579         mAdapter.onBindViewHolder(mViewHolder, 3);
580         assertEquals(1, mAdapter.getGroupSize(3));
581         assertEquals(TEST_NUMBER_3, mViewHolder.number);
582     }
583 
testVoicemailArchive()584     public void testVoicemailArchive() {
585         setUpArchiveAdapter();
586         createVoicemailArchiveCallLogEntry();
587 
588         mAdapter.changeCursorVoicemail(mCursor);
589         mAdapter.onBindViewHolder(mViewHolder, 0);
590 
591         assertEquals(Uri.parse(mViewHolder.voicemailUri),
592                 ContentUris.withAppendedId(
593                         VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, 0));
594         assertNull(mViewHolder.primaryActionButtonView.getTag());
595     }
596 
createCallLogEntry()597     private void createCallLogEntry() {
598         createCallLogEntry(TEST_NUMBER);
599     }
600 
createCallLogEntry(String testNumber)601     private void createCallLogEntry(String testNumber) {
602         createCallLogEntry(testNumber, EMPTY_STRING, NO_VALUE_SET, NO_VALUE_SET);
603     }
604 
createPrivateCallLogEntry()605     private void createPrivateCallLogEntry() {
606         createCallLogEntry(
607                 EMPTY_STRING,
608                 EMPTY_STRING,
609                 Calls.PRESENTATION_RESTRICTED,
610                 AppCompatConstants.CALLS_INCOMING_TYPE);
611     }
612 
createUnknownCallLogEntry()613     private void createUnknownCallLogEntry() {
614         createCallLogEntry(
615                 EMPTY_STRING,
616                 EMPTY_STRING,
617                 Calls.PRESENTATION_UNKNOWN,
618                 AppCompatConstants.CALLS_INCOMING_TYPE);
619     }
620 
createVoicemailCallLogEntry()621     private void createVoicemailCallLogEntry() {
622         createCallLogEntry(TEST_NUMBER, EMPTY_STRING, NO_VALUE_SET, Calls.VOICEMAIL_TYPE);
623     }
624 
createVoicemailArchiveCallLogEntry()625     private void createVoicemailArchiveCallLogEntry() {
626         createCallLogEntry(TEST_NUMBER, EMPTY_STRING, NO_VALUE_SET, ARCHIVE_TYPE);
627     }
628 
createCallLogEntry(String number, String postDialDigits, int presentation, int type)629     private void createCallLogEntry(String number, String postDialDigits, int presentation,
630             int type) {
631         Object[] values = getValues(number, postDialDigits, presentation, type);
632         mCursor.addRow(values);
633     }
634 
createCallLogEntry(String number, String postDialDigits, String viaNumber, int presentation, int type)635     private void createCallLogEntry(String number, String postDialDigits, String viaNumber,
636             int presentation, int type) {
637         Object[] values = getValues(number, postDialDigits, viaNumber, presentation, type);
638         mCursor.addRow(values);
639     }
640 
createCallLogEntryWithCachedValues(boolean inject)641     private void createCallLogEntryWithCachedValues(boolean inject) {
642         createCallLogEntryWithCachedValues(
643                 TEST_NUMBER,
644                 NO_VALUE_SET,
645                 TEST_CACHED_NAME_PRIMARY,
646                 TEST_CACHED_NUMBER_TYPE,
647                 TEST_CACHED_NUMBER_LABEL,
648                 EMPTY_STRING,
649                 inject);
650     }
651 
createCallLogEntryWithCachedValues( String cachedName, int cachedNumberType, String cachedNumberLabel)652     private void createCallLogEntryWithCachedValues(
653             String cachedName, int cachedNumberType, String cachedNumberLabel) {
654         createCallLogEntryWithCachedValues(
655                 TEST_NUMBER,
656                 NO_VALUE_SET,
657                 cachedName,
658                 cachedNumberType,
659                 cachedNumberLabel,
660                 EMPTY_STRING,
661                 false /* inject */);
662     }
663 
664     /**
665      * Inserts a new call log entry
666      *
667      * It includes the values for the cached contact associated with the number.
668      *
669      * @param number The phone number.
670      * @param type Valid value of {@code Calls.TYPE}.
671      * @param cachedName The name of the contact with this number
672      * @param cachedNumberType The type of the number, from the contact with this number.
673      * @param cachedNumberLabel The label of the number, from the contact with this number.
674      * @param cachedFormattedNumber The formatted number, from the contact with this number.
675      * @param inject Whether to inject the contact info into the adapter's ContactInfoCache.
676     */
createCallLogEntryWithCachedValues( String number, int type, String cachedName, int cachedNumberType, String cachedNumberLabel, String cachedFormattedNumber, boolean inject)677     private void createCallLogEntryWithCachedValues(
678             String number,
679             int type,
680             String cachedName,
681             int cachedNumberType,
682             String cachedNumberLabel,
683             String cachedFormattedNumber,
684             boolean inject) {
685         Object[] values = getValues(number, EMPTY_STRING, NO_VALUE_SET, type);
686         values[CallLogQuery.CACHED_NAME] = cachedName;
687         values[CallLogQuery.CACHED_NUMBER_TYPE] = cachedNumberType;
688         values[CallLogQuery.CACHED_NUMBER_LABEL] = cachedNumberLabel;
689         values[CallLogQuery.CACHED_FORMATTED_NUMBER] = cachedFormattedNumber;
690 
691         mCursor.addRow(values);
692 
693         if (inject) {
694             ContactInfo contactInfo =
695                     createContactInfo(cachedName, cachedName, cachedNumberType, cachedNumberLabel);
696             mAdapter.injectContactInfoForTest(number, TEST_COUNTRY_ISO, contactInfo);
697         }
698     }
699 
700     /**
701      * @param number The phone number.
702      * @param postDialDigits The post dial digits dialed (if any)
703      * @param presentation Number representing display rules for "allowed",
704      *               "payphone", "restricted", or "unknown".
705      * @param type The type of the call (outgoing/ingoing)
706      */
getValues( String number, String postDialDigits, int presentation, int type)707     private Object[] getValues(
708             String number,
709             String postDialDigits,
710             int presentation,
711             int type) {
712         return getValues(number, postDialDigits, "", presentation, type);
713     }
714 
715     /**
716      * @param number The phone number.
717      * @param postDialDigits The post dial digits dialed (if any)
718      * @param viaNumber The secondary number that the call was placed via
719      * @param presentation Number representing display rules for "allowed",
720      *               "payphone", "restricted", or "unknown".
721      * @param type The type of the call (outgoing/ingoing)
722      */
getValues( String number, String postDialDigits, String viaNumber, int presentation, int type)723     private Object[] getValues(
724             String number,
725             String postDialDigits,
726             String viaNumber,
727             int presentation,
728             int type) {
729         Object[] values = CallLogQueryTestUtils.createTestValues();
730 
731         values[CallLogQuery.ID] = mCursor.getCount();
732         values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
733         values[CallLogQuery.DATE] = new Date().getTime();
734         values[CallLogQuery.DURATION] = mRandom.nextInt(10 * 60);
735 
736         if (!TextUtils.isEmpty(number)) {
737             values[CallLogQuery.NUMBER] = number;
738         }
739         if (!TextUtils.isEmpty(postDialDigits) && CompatUtils.isNCompatible()) {
740             values[CallLogQuery.POST_DIAL_DIGITS] = postDialDigits;
741         }
742         if (!TextUtils.isEmpty(viaNumber) && CompatUtils.isNCompatible()) {
743             values[CallLogQuery.VIA_NUMBER] = viaNumber;
744         }
745         if (presentation != NO_VALUE_SET) {
746             values[CallLogQuery.NUMBER_PRESENTATION] = presentation;
747         }
748         if (type != NO_VALUE_SET) {
749             values[CallLogQuery.CALL_TYPE] = type;
750         }
751         if (type == AppCompatConstants.CALLS_VOICEMAIL_TYPE) {
752             values[CallLogQuery.VOICEMAIL_URI] = ContentUris.withAppendedId(
753                     VoicemailContract.Voicemails.CONTENT_URI, mCursor.getCount());
754         }
755         if (type == ARCHIVE_TYPE) {
756             values[CallLogQuery.VOICEMAIL_URI] = ContentUris.withAppendedId(
757                     VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, mCursor.getCount());
758         }
759 
760         return values;
761     }
762 
createContactInfo()763     private ContactInfo createContactInfo() {
764         return createContactInfo(
765                 TEST_CACHED_NAME_PRIMARY,
766                 TEST_CACHED_NAME_ALTERNATIVE);
767     }
768 
createContactInfo(String namePrimary, String nameAlternative)769     private ContactInfo createContactInfo(String namePrimary, String nameAlternative) {
770         return createContactInfo(
771                 namePrimary,
772                 nameAlternative,
773                 TEST_CACHED_NUMBER_TYPE,
774                 TEST_CACHED_NUMBER_LABEL);
775     }
776 
777     /** Returns a contact info with default values. */
createContactInfo(String namePrimary, String nameAlternative, int type, String label)778     private ContactInfo createContactInfo(String namePrimary, String nameAlternative, int type, String label) {
779         ContactInfo info = new ContactInfo();
780         info.number = TEST_NUMBER;
781         info.name = namePrimary;
782         info.nameAlternative = nameAlternative;
783         info.type = type;
784         info.label = label;
785         info.formattedNumber = TEST_FORMATTED_NUMBER;
786         info.normalizedNumber = TEST_NUMBER;
787         info.lookupUri = TEST_LOOKUP_URI;
788         return info;
789     }
790 
791     // Asserts that the name text view is shown and contains the given text.
assertNameIs(CallLogListItemViewHolder viewHolder, String name)792     private void assertNameIs(CallLogListItemViewHolder viewHolder, String name) {
793         assertEquals(View.VISIBLE, viewHolder.phoneCallDetailsViews.nameView.getVisibility());
794         assertEquals(name, viewHolder.phoneCallDetailsViews.nameView.getText());
795     }
796 
797     // Asserts that the label text view contains the given text.
assertLabel( CallLogListItemViewHolder viewHolder, CharSequence number, CharSequence label)798     private void assertLabel(
799             CallLogListItemViewHolder viewHolder, CharSequence number, CharSequence label) {
800         if (label != null) {
801             assertTrue(viewHolder.phoneCallDetailsViews.callLocationAndDate.getText()
802                     .toString().contains(label));
803         }
804     }
805 
assertHasCallAction(CallLogListItemViewHolder viewHolder)806     private void assertHasCallAction(CallLogListItemViewHolder viewHolder) {
807         assertHasCallActionToGivenNumber(viewHolder, TEST_NUMBER);
808     }
809 
assertHasCallActionToGivenNumber(CallLogListItemViewHolder viewHolder, String number)810     private void assertHasCallActionToGivenNumber(CallLogListItemViewHolder viewHolder,
811             String number) {
812         IntentProvider intentProvider =
813                 (IntentProvider) viewHolder.primaryActionButtonView.getTag();
814         Intent intent = intentProvider.getIntent(getContext());
815         assertEquals(TestConstants.CALL_INTENT_ACTION, intent.getAction());
816         assertEquals(Uri.parse("tel:" + Uri.encode(number)), intent.getData());
817     }
818 
819     /** Returns the label associated with a given phone type. */
getTypeLabel(int phoneType)820     private CharSequence getTypeLabel(int phoneType) {
821         return Phone.getTypeLabel(getContext().getResources(), phoneType, "");
822     }
823 
setUpArchiveAdapter()824     private void setUpArchiveAdapter() {
825         // Use a call fetcher that does not do anything.
826         CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
827             @Override
828             public void fetchCalls() {}
829         };
830 
831         ContactInfoHelper fakeContactInfoHelper =
832                 new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
833                     @Override
834                     public ContactInfo lookupNumber(String number, String countryIso) {
835                         ContactInfo info = new ContactInfo();
836                         info.number = number;
837                         info.formattedNumber = number;
838                         return info;
839                     }
840                 };
841 
842         mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper,
843                 CallLogAdapter.ACTIVITY_TYPE_ARCHIVE);
844     }
845 
846     /// Subclass of {@link CallLogAdapter} used in tests to intercept certain calls.
847     private static final class TestCallLogAdapter extends CallLogAdapter {
TestCallLogAdapter(Context context, CallFetcher callFetcher, ContactInfoHelper contactInfoHelper, int mActivity)848         public TestCallLogAdapter(Context context, CallFetcher callFetcher,
849                 ContactInfoHelper contactInfoHelper, int mActivity) {
850             super(context, callFetcher, contactInfoHelper, null,
851                     mActivity);
852             mContactInfoCache = new TestContactInfoCache(
853                     contactInfoHelper, mOnContactInfoChangedListener);
854         }
855 
getContactInfoCache()856         public TestContactInfoCache getContactInfoCache() {
857             return (TestContactInfoCache) mContactInfoCache;
858         }
859 
showVoicemailPromoCard(boolean show)860         public void showVoicemailPromoCard(boolean show) {
861             mShowVoicemailPromoCard = show;
862         }
863     }
864 
865     private static final class TestContactInfoCache extends ContactInfoCache {
866         public static class Request {
867             public final String number;
868             public final String countryIso;
869             public final ContactInfo callLogInfo;
870             public final boolean immediate;
871 
Request(String number, String countryIso, ContactInfo callLogInfo, boolean immediate)872             public Request(String number, String countryIso, ContactInfo callLogInfo,
873                     boolean immediate) {
874                 this.number = number;
875                 this.countryIso = countryIso;
876                 this.callLogInfo = callLogInfo;
877                 this.immediate = immediate;
878             }
879         }
880 
881         public final List<Request> requests = Lists.newArrayList();
882 
883         /**
884          * Dummy contactInfo to return in the even that the getValue method has been mocked
885          */
886         private ContactInfo mContactInfo;
887 
TestContactInfoCache( ContactInfoHelper contactInfoHelper, OnContactInfoChangedListener listener)888         public TestContactInfoCache(
889                 ContactInfoHelper contactInfoHelper, OnContactInfoChangedListener listener) {
890             super(contactInfoHelper, listener);
891         }
892 
893         /**
894          * Sets the given value to be returned by all calls to
895          * {@link #getValue(String, String, ContactInfo)}
896          *
897          * @param contactInfo the contactInfo
898          */
mockGetValue(ContactInfo contactInfo)899         public void mockGetValue(ContactInfo contactInfo) {
900             this.mContactInfo = contactInfo;
901         }
902 
903         @Override
getValue(String number, String countryIso, ContactInfo cachedContactInfo)904         public ContactInfo getValue(String number, String countryIso,
905                 ContactInfo cachedContactInfo) {
906             if (mContactInfo != null) {
907                 return mContactInfo;
908             }
909             return super.getValue(number, countryIso, cachedContactInfo);
910         }
911 
912         @Override
enqueueRequest(String number, String countryIso, ContactInfo callLogInfo, boolean immediate)913         protected void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
914                 boolean immediate) {
915             requests.add(new Request(number, countryIso, callLogInfo, immediate));
916         }
917     }
918 }
919