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
17package com.android.contacts.calllog;
18
19import com.android.contacts.CallDetailActivity;
20import com.android.contacts.R;
21import com.android.contacts.test.FragmentTestActivity;
22import com.android.internal.telephony.CallerInfo;
23
24import android.app.FragmentManager;
25import android.app.FragmentTransaction;
26import android.content.ComponentName;
27import android.content.ContentUris;
28import android.content.Intent;
29import android.content.res.Resources;
30import android.database.MatrixCursor;
31import android.graphics.Bitmap;
32import android.graphics.drawable.BitmapDrawable;
33import android.net.Uri;
34import android.provider.CallLog.Calls;
35import android.provider.ContactsContract.CommonDataKinds.Phone;
36import android.provider.VoicemailContract;
37import android.telephony.PhoneNumberUtils;
38import android.telephony.TelephonyManager;
39import android.test.ActivityInstrumentationTestCase2;
40import android.test.suitebuilder.annotation.LargeTest;
41import android.test.suitebuilder.annotation.MediumTest;
42import android.view.View;
43import android.widget.FrameLayout;
44
45import java.util.Date;
46import java.util.Formatter;
47import java.util.HashMap;
48import 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
61public 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
102    public CallLogFragmentTest() {
103        super("com.android.contacts", FragmentTestActivity.class);
104        mIndex = 1;
105        mRnd = new Random();
106    }
107
108    @Override
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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. */
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     */
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     */
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     */
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     */
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}. */
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     */
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     */
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     */
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. */
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     */
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 numberLabel) {
623        assertEquals(View.VISIBLE, views.phoneCallDetailsViews.numberView.getVisibility());
624        final CharSequence expectedText;
625        if (numberLabel == null) {
626            expectedText = number;
627        } else {
628            expectedText = numberLabel + " " + number;
629        }
630        assertEquals(expectedText, views.phoneCallDetailsViews.numberView.getText().toString());
631    }
632}
633