• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.contacts.calllog;
18 
19 import android.app.FragmentManager;
20 import android.app.FragmentTransaction;
21 import android.content.ComponentName;
22 import android.content.ContentUris;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.MatrixCursor;
26 import android.graphics.Bitmap;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.net.Uri;
29 import android.provider.CallLog.Calls;
30 import android.provider.ContactsContract.CommonDataKinds.Phone;
31 import android.provider.VoicemailContract;
32 import android.telephony.PhoneNumberUtils;
33 import android.telephony.TelephonyManager;
34 import android.test.ActivityInstrumentationTestCase2;
35 import android.test.suitebuilder.annotation.LargeTest;
36 import android.test.suitebuilder.annotation.MediumTest;
37 import android.view.View;
38 import android.widget.FrameLayout;
39 
40 import com.android.contacts.CallDetailActivity;
41 import com.android.contacts.R;
42 import com.android.contacts.test.FragmentTestActivity;
43 import com.android.internal.telephony.CallerInfo;
44 
45 import java.util.Date;
46 import java.util.Formatter;
47 import java.util.HashMap;
48 import java.util.Random;
49 
50 /**
51  * Tests for the contact call list activity.
52  *
53  * Running all tests:
54  *
55  *   runtest contacts
56  * or
57  *   adb shell am instrument \
58  *     -w com.android.contacts.tests/android.test.InstrumentationTestRunner
59  */
60 @LargeTest
61 public class CallLogFragmentTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
62     private static final int RAND_DURATION = -1;
63     private static final long NOW = -1L;
64 
65     /** A test value for the URI of a contact. */
66     private static final Uri TEST_LOOKUP_URI = Uri.parse("content://contacts/2");
67     /** A test value for the country ISO of the phone number in the call log. */
68     private static final String TEST_COUNTRY_ISO = "US";
69     /** A phone number to be used in tests. */
70     private static final String TEST_NUMBER = "12125551000";
71     /** The formatted version of {@link #TEST_NUMBER}. */
72     private static final String TEST_FORMATTED_NUMBER = "1 212-555-1000";
73 
74     /** The activity in which we are hosting the fragment. */
75     private FragmentTestActivity mActivity;
76     private CallLogFragment mFragment;
77     private FrameLayout mParentView;
78     /**
79      * The adapter used by the fragment to build the rows in the call log. We use it with our own in
80      * memory database.
81      */
82     private CallLogAdapter mAdapter;
83     private String mVoicemail;
84 
85     // In memory array to hold the rows corresponding to the 'calls' table.
86     private MatrixCursor mCursor;
87     private int mIndex;  // Of the next row.
88 
89     private Random mRnd;
90 
91     // References to the icons bitmaps used to build the list are stored in a
92     // map mIcons. The keys to retrieve the icons are:
93     // Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE and Calls.MISSED_TYPE.
94     private HashMap<Integer, Bitmap> mCallTypeIcons;
95 
96     // An item in the call list. All the methods performing checks use it.
97     private CallLogListItemViews mItem;
98     // The list of views representing the data in the DB. View are in
99     // reverse order compare to the DB.
100     private View[] mList;
101 
CallLogFragmentTest()102     public CallLogFragmentTest() {
103         super("com.android.contacts", FragmentTestActivity.class);
104         mIndex = 1;
105         mRnd = new Random();
106     }
107 
108     @Override
setUp()109     public void setUp() {
110         mActivity = getActivity();
111         // Needed by the CallLogFragment.
112         mActivity.setTheme(R.style.DialtactsTheme);
113 
114         // Create the fragment and load it into the activity.
115         mFragment = new CallLogFragment();
116         FragmentManager fragmentManager = mActivity.getFragmentManager();
117         FragmentTransaction transaction = fragmentManager.beginTransaction();
118         transaction.add(R.id.fragment, mFragment);
119         transaction.commit();
120         // Wait for the fragment to be loaded.
121         getInstrumentation().waitForIdleSync();
122 
123         mVoicemail = TelephonyManager.getDefault().getVoiceMailNumber();
124         mAdapter = mFragment.getAdapter();
125         // Do not process requests for details during tests. This would start a background thread,
126         // which makes the tests flaky.
127         mAdapter.disableRequestProcessingForTest();
128         mAdapter.stopRequestProcessing();
129         mParentView = new FrameLayout(mActivity);
130         mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
131         buildIconMap();
132     }
133 
134     /**
135      * Checks that the call icon is not visible for private and
136      * unknown numbers.
137      * Use 2 passes, one where new views are created and one where
138      * half of the total views are updated and the other half created.
139      */
140     @MediumTest
testCallViewIsNotVisibleForPrivateAndUnknownNumbers()141     public void testCallViewIsNotVisibleForPrivateAndUnknownNumbers() {
142         final int SIZE = 100;
143         mList = new View[SIZE];
144 
145         // Insert the first batch of entries.
146         mCursor.moveToFirst();
147         insertRandomEntries(SIZE / 2);
148         int startOfSecondBatch = mCursor.getPosition();
149 
150         buildViewListFromDb();
151         checkCallStatus();
152 
153         // Append the rest of the entries. We keep the first set of
154         // views around so they get updated and not built from
155         // scratch, this exposes some bugs that are not there when the
156         // call log is launched for the 1st time but show up when the
157         // call log gets updated afterwards.
158         mCursor.move(startOfSecondBatch);
159         insertRandomEntries(SIZE / 2);
160 
161         buildViewListFromDb();
162         checkCallStatus();
163     }
164 
165     @MediumTest
testCallAndGroupViews_GroupView()166     public void testCallAndGroupViews_GroupView() {
167         mCursor.moveToFirst();
168         insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
169         insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
170         insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
171         View view = mAdapter.newGroupView(getActivity(), mParentView);
172         mAdapter.bindGroupView(view, getActivity(), mCursor, 3, false);
173         assertNotNull(view.findViewById(R.id.secondary_action_icon));
174     }
175 
176     @MediumTest
testCallAndGroupViews_StandAloneView()177     public void testCallAndGroupViews_StandAloneView() {
178         mCursor.moveToFirst();
179         insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
180         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
181         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
182         assertNotNull(view.findViewById(R.id.secondary_action_icon));
183     }
184 
185     @MediumTest
testCallAndGroupViews_ChildView()186     public void testCallAndGroupViews_ChildView() {
187         mCursor.moveToFirst();
188         insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
189         View view = mAdapter.newChildView(getActivity(), mParentView);
190         mAdapter.bindChildView(view, getActivity(), mCursor);
191         assertNotNull(view.findViewById(R.id.secondary_action_icon));
192     }
193 
194     @MediumTest
testBindView_NumberOnlyNoCache()195     public void testBindView_NumberOnlyNoCache() {
196         mCursor.moveToFirst();
197         insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
198         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
199         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
200 
201         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
202         assertNameIs(views, TEST_NUMBER);
203     }
204 
205     @MediumTest
testBindView_NumberOnlyDbCachedFormattedNumber()206     public void testBindView_NumberOnlyDbCachedFormattedNumber() {
207         mCursor.moveToFirst();
208         Object[] values = getValuesToInsert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
209         values[CallLogQuery.CACHED_FORMATTED_NUMBER] = TEST_FORMATTED_NUMBER;
210         insertValues(values);
211         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
212         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
213 
214         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
215         assertNameIs(views, TEST_FORMATTED_NUMBER);
216     }
217 
218     @MediumTest
testBindView_WithCachedName()219     public void testBindView_WithCachedName() {
220         mCursor.moveToFirst();
221         insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
222                 "John Doe", Phone.TYPE_HOME, "");
223         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
224         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
225 
226         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
227         assertNameIs(views, "John Doe");
228         assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
229     }
230 
231     @MediumTest
testBindView_UriNumber()232     public void testBindView_UriNumber() {
233         mCursor.moveToFirst();
234         insertWithCachedValues("sip:johndoe@gmail.com", NOW, 0, Calls.INCOMING_TYPE,
235                 "John Doe", Phone.TYPE_HOME, "");
236         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
237         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
238 
239         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
240         assertNameIs(views, "John Doe");
241         assertNumberAndLabelAre(views, "sip:johndoe@gmail.com", null);
242     }
243 
244     @MediumTest
testBindView_HomeLabel()245     public void testBindView_HomeLabel() {
246         mCursor.moveToFirst();
247         insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
248                 "John Doe", Phone.TYPE_HOME, "");
249         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
250         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
251 
252         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
253         assertNameIs(views, "John Doe");
254         assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
255     }
256 
257     @MediumTest
testBindView_WorkLabel()258     public void testBindView_WorkLabel() {
259         mCursor.moveToFirst();
260         insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
261                 "John Doe", Phone.TYPE_WORK, "");
262         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
263         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
264 
265         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
266         assertNameIs(views, "John Doe");
267         assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_WORK));
268     }
269 
270     @MediumTest
testBindView_CustomLabel()271     public void testBindView_CustomLabel() {
272         mCursor.moveToFirst();
273         String numberLabel = "My label";
274         insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
275                 "John Doe", Phone.TYPE_CUSTOM, numberLabel);
276         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
277         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
278 
279         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
280         assertNameIs(views, "John Doe");
281         assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, numberLabel);
282     }
283 
284     @MediumTest
testBindView_WithQuickContactBadge()285     public void testBindView_WithQuickContactBadge() {
286         mCursor.moveToFirst();
287         insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
288                 "John Doe", Phone.TYPE_HOME, "");
289         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
290         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
291 
292         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
293         assertTrue(views.quickContactView.isEnabled());
294     }
295 
296     @MediumTest
testBindView_WithoutQuickContactBadge()297     public void testBindView_WithoutQuickContactBadge() {
298         mCursor.moveToFirst();
299         insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
300         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
301         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
302 
303         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
304         assertFalse(views.quickContactView.isEnabled());
305     }
306 
307     @MediumTest
testBindView_CallButton()308     public void testBindView_CallButton() {
309         mCursor.moveToFirst();
310         insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
311         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
312         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
313 
314         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
315         IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
316         Intent intent = intentProvider.getIntent(mActivity);
317         // Starts a call.
318         assertEquals(Intent.ACTION_CALL_PRIVILEGED, intent.getAction());
319         // To the entry's number.
320         assertEquals(Uri.parse("tel:" + TEST_NUMBER), intent.getData());
321     }
322 
323     @MediumTest
testBindView_PlayButton()324     public void testBindView_PlayButton() {
325         mCursor.moveToFirst();
326         insertVoicemail(TEST_NUMBER, NOW, 0);
327         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
328         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
329 
330         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
331         IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
332         Intent intent = intentProvider.getIntent(mActivity);
333         // Starts the call detail activity.
334         assertEquals(new ComponentName(mActivity, CallDetailActivity.class),
335                 intent.getComponent());
336         // With the given entry.
337         assertEquals(ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, 1),
338                 intent.getData());
339         // With the URI of the voicemail.
340         assertEquals(
341                 ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, 1),
342                 intent.getParcelableExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI));
343         // And starts playback.
344         assertTrue(
345                 intent.getBooleanExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false));
346     }
347 
348     /** Returns the label associated with a given phone type. */
getTypeLabel(int phoneType)349     private CharSequence getTypeLabel(int phoneType) {
350         return Phone.getTypeLabel(getActivity().getResources(), phoneType, "");
351     }
352 
353     //
354     // HELPERS to check conditions on the DB/views
355     //
356     /**
357      * Go over all the views in the list and check that the Call
358      * icon's visibility matches the nature of the number.
359      */
checkCallStatus()360     private void checkCallStatus() {
361         for (int i = 0; i < mList.length; i++) {
362             if (null == mList[i]) {
363                 break;
364             }
365             mItem = (CallLogListItemViews) mList[i].getTag();
366             String number = getPhoneNumberForListEntry(i);
367             if (CallerInfo.PRIVATE_NUMBER.equals(number) ||
368                 CallerInfo.UNKNOWN_NUMBER.equals(number)) {
369                 assertFalse(View.VISIBLE == mItem.secondaryActionView.getVisibility());
370             } else {
371                 assertEquals(View.VISIBLE, mItem.secondaryActionView.getVisibility());
372             }
373         }
374     }
375 
376 
377     //
378     // HELPERS to setup the tests.
379     //
380 
381     /**
382      * Get the Bitmap from the icons in the contacts package.
383      */
getBitmap(String resName)384     private Bitmap getBitmap(String resName) {
385         Resources r = mActivity.getResources();
386         int resid = r.getIdentifier(resName, "drawable", "com.android.contacts");
387         BitmapDrawable d = (BitmapDrawable) r.getDrawable(resid);
388         assertNotNull(d);
389         return d.getBitmap();
390     }
391 
392     /**
393      * Fetch all the icons we need in tests from the contacts app and store them in a map.
394      */
buildIconMap()395     private void buildIconMap() {
396         mCallTypeIcons = new HashMap<Integer, Bitmap>(3);
397 
398         mCallTypeIcons.put(Calls.INCOMING_TYPE, getBitmap("ic_call_incoming_holo_dark"));
399         mCallTypeIcons.put(Calls.MISSED_TYPE, getBitmap("ic_call_missed_holo_dark"));
400         mCallTypeIcons.put(Calls.OUTGOING_TYPE, getBitmap("ic_call_outgoing_holo_dark"));
401     }
402 
403     //
404     // HELPERS to build/update the call entries (views) from the DB.
405     //
406 
407     /**
408      * Read the DB and foreach call either update the existing view if
409      * one exists already otherwise create one.
410      * The list is build from a DESC view of the DB (last inserted entry is first).
411      */
buildViewListFromDb()412     private void buildViewListFromDb() {
413         int i = 0;
414         mCursor.moveToLast();
415         while(!mCursor.isBeforeFirst()) {
416             if (null == mList[i]) {
417                 mList[i] = mAdapter.newStandAloneView(mActivity, mParentView);
418             }
419             mAdapter.bindStandAloneView(mList[i], mActivity, mCursor);
420             mCursor.moveToPrevious();
421             i++;
422         }
423     }
424 
425     /** Returns the number associated with the given entry in {{@link #mList}. */
getPhoneNumberForListEntry(int index)426     private String getPhoneNumberForListEntry(int index) {
427         // The entries are added backward, so count from the end of the cursor.
428         mCursor.moveToPosition(mCursor.getCount() - index - 1);
429         return mCursor.getString(CallLogQuery.NUMBER);
430     }
431 
432     //
433     // HELPERS to insert numbers in the call log DB.
434     //
435 
436     /**
437      * Insert a certain number of random numbers in the DB. Makes sure
438      * there is at least one private and one unknown number in the DB.
439      * @param num Of entries to be inserted.
440      */
insertRandomEntries(int num)441     private void insertRandomEntries(int num) {
442         if (num < 10) {
443             throw new IllegalArgumentException("num should be >= 10");
444         }
445         boolean privateOrUnknownOrVm[];
446         privateOrUnknownOrVm = insertRandomRange(0, num - 2);
447 
448         if (privateOrUnknownOrVm[0] && privateOrUnknownOrVm[1]) {
449             insertRandomRange(num - 2, num);
450         } else {
451             insertPrivate(NOW, RAND_DURATION);
452             insertUnknown(NOW, RAND_DURATION);
453         }
454     }
455 
456     /**
457      * Insert a new call entry in the test DB.
458      *
459      * It includes the values for the cached contact associated with the number.
460      *
461      * @param number The phone number. For unknown and private numbers,
462      *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
463      * @param date In millisec since epoch. Use NOW to use the current time.
464      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
465      * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
466      * @param cachedName the name of the contact with this number
467      * @param cachedNumberType the type of the number, from the contact with this number
468      * @param cachedNumberLabel the label of the number, from the contact with this number
469      */
insertWithCachedValues(String number, long date, int duration, int type, String cachedName, int cachedNumberType, String cachedNumberLabel)470     private void insertWithCachedValues(String number, long date, int duration, int type,
471             String cachedName, int cachedNumberType, String cachedNumberLabel) {
472         insert(number, date, duration, type);
473         ContactInfo contactInfo = new ContactInfo();
474         contactInfo.lookupUri = TEST_LOOKUP_URI;
475         contactInfo.name = cachedName;
476         contactInfo.type = cachedNumberType;
477         contactInfo.label = cachedNumberLabel;
478         String formattedNumber = PhoneNumberUtils.formatNumber(number, TEST_COUNTRY_ISO);
479         if (formattedNumber == null) {
480             formattedNumber = number;
481         }
482         contactInfo.formattedNumber = formattedNumber;
483         contactInfo.normalizedNumber = number;
484         contactInfo.photoId = 0;
485         mAdapter.injectContactInfoForTest(number, TEST_COUNTRY_ISO, contactInfo);
486     }
487 
488     /**
489      * Insert a new call entry in the test DB.
490      * @param number The phone number. For unknown and private numbers,
491      *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
492      * @param date In millisec since epoch. Use NOW to use the current time.
493      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
494      * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
495      */
insert(String number, long date, int duration, int type)496     private void insert(String number, long date, int duration, int type) {
497         insertValues(getValuesToInsert(number, date, duration, type));
498     }
499 
500     /** Inserts the given values in the cursor. */
insertValues(Object[] values)501     private void insertValues(Object[] values) {
502         mCursor.addRow(values);
503         ++mIndex;
504     }
505 
506     /**
507      * Returns the values for a new call entry.
508      *
509      * @param number The phone number. For unknown and private numbers,
510      *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
511      * @param date In millisec since epoch. Use NOW to use the current time.
512      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
513      * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
514      */
getValuesToInsert(String number, long date, int duration, int type)515     private Object[] getValuesToInsert(String number, long date, int duration, int type) {
516         Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
517         values[CallLogQuery.ID] = mIndex;
518         values[CallLogQuery.NUMBER] = number;
519         values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
520         values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
521         if (mVoicemail != null && mVoicemail.equals(number)) {
522             assertEquals(Calls.OUTGOING_TYPE, type);
523         }
524         values[CallLogQuery.CALL_TYPE] = type;
525         values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
526         values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
527         return values;
528     }
529 
530     /**
531      * Insert a new voicemail entry in the test DB.
532      * @param number The phone number. For unknown and private numbers,
533      *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
534      * @param date In millisec since epoch. Use NOW to use the current time.
535      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
536      */
537     private void insertVoicemail(String number, long date, int duration) {
538         Object[] values = getValuesToInsert(number, date, duration, Calls.VOICEMAIL_TYPE);
539         // Must have the same index as the row.
540         values[CallLogQuery.VOICEMAIL_URI] =
541                 ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, mIndex);
542         insertValues(values);
543     }
544 
545     /**
546      * Insert a new private call entry in the test DB.
547      * @param date In millisec since epoch. Use NOW to use the current time.
548      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
549      */
550     private void insertPrivate(long date, int duration) {
551         insert(CallerInfo.PRIVATE_NUMBER, date, duration, Calls.INCOMING_TYPE);
552     }
553 
554     /**
555      * Insert a new unknown call entry in the test DB.
556      * @param date In millisec since epoch. Use NOW to use the current time.
557      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
558      */
559     private void insertUnknown(long date, int duration) {
560         insert(CallerInfo.UNKNOWN_NUMBER, date, duration, Calls.INCOMING_TYPE);
561     }
562 
563     /**
564      * Insert a new call to voicemail entry in the test DB.
565      * @param date In millisec since epoch. Use NOW to use the current time.
566      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
567      */
568     private void insertCalltoVoicemail(long date, int duration) {
569         // mVoicemail may be null
570         if (mVoicemail != null) {
571             insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
572         }
573     }
574 
575     /**
576      * Insert a range [start, end) of random numbers in the DB. For
577      * each row, there is a 1/10 probability that the number will be
578      * marked as PRIVATE or UNKNOWN or VOICEMAIL. For regular numbers, a number is
579      * inserted, its last 4 digits will be the number of the iteration
580      * in the range.
581      * @param start Of the range.
582      * @param end Of the range (excluded).
583      * @return An array with 2 booleans [0 = private number, 1 =
584      * unknown number, 2 = voicemail] to indicate if at least one
585      * private or unknown or voicemail number has been inserted. Since
586      * the numbers are random some tests may want to enforce the
587      * insertion of such numbers.
588      */
589     // TODO: Should insert numbers with contact entries too.
590     private boolean[] insertRandomRange(int start, int end) {
591         boolean[] privateOrUnknownOrVm = new boolean[] {false, false, false};
592 
593         for (int i = start; i < end; i++ ) {
594             int type = mRnd.nextInt(10);
595 
596             if (0 == type) {
597                 insertPrivate(NOW, RAND_DURATION);
598                 privateOrUnknownOrVm[0] = true;
599             } else if (1 == type) {
600                 insertUnknown(NOW, RAND_DURATION);
601                 privateOrUnknownOrVm[1] = true;
602             } else if (2 == type) {
603                 insertCalltoVoicemail(NOW, RAND_DURATION);
604                 privateOrUnknownOrVm[2] = true;
605             } else {
606                 int inout = mRnd.nextBoolean() ? Calls.OUTGOING_TYPE :  Calls.INCOMING_TYPE;
607                 String number = new Formatter().format("1800123%04d", i).toString();
608                 insert(number, NOW, RAND_DURATION, inout);
609             }
610         }
611         return privateOrUnknownOrVm;
612     }
613 
614     /** Asserts that the name text view is shown and contains the given text. */
615     private void assertNameIs(CallLogListItemViews views, String name) {
616         assertEquals(View.VISIBLE, views.phoneCallDetailsViews.nameView.getVisibility());
617         assertEquals(name, views.phoneCallDetailsViews.nameView.getText());
618     }
619 
620     /** Asserts that the number and label text view contains the given text. */
621     private void assertNumberAndLabelAre(CallLogListItemViews views, CharSequence number,
622             CharSequence label) {
623         assertEquals(View.VISIBLE, views.phoneCallDetailsViews.numberView.getVisibility());
624         assertEquals(number, views.phoneCallDetailsViews.numberView.getText().toString());
625 
626         assertEquals(label == null ? View.GONE : View.VISIBLE,
627                 views.phoneCallDetailsViews.labelView.getVisibility());
628         if (label != null) {
629             assertEquals(label, views.phoneCallDetailsViews.labelView.getText().toString());
630         }
631     }
632 }
633