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