Disable the call icon in the logs for private and unknown numbers.

We cannot callback private numbers (ie number with a caller id blocked)
so we need to disable the call icon.
Same for Unknown ones (e.g private number on GSM shows as unknown).

Added a test to make sure call icon is not visible on the right entries.

Bug:2144575
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index decce7b..d892def 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -453,7 +453,11 @@
                 // Format the cached call_log phone number
                 formattedNumber = formatPhoneNumber(number);
             }
-            // Set the text lines
+            // Set the text lines and call icon.
+            // Assumes the call back feature is on most of the
+            // time. For private and unknown numbers: hide it.
+            views.callView.setVisibility(View.VISIBLE);
+
             if (!TextUtils.isEmpty(name)) {
                 views.line1View.setText(name);
                 views.labelView.setVisibility(View.VISIBLE);
@@ -470,8 +474,10 @@
             } else {
                 if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
                     number = getString(R.string.unknown);
+                    views.callView.setVisibility(View.INVISIBLE);
                 } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
                     number = getString(R.string.private_num);
+                    views.callView.setVisibility(View.INVISIBLE);
                 } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
                     number = getString(R.string.payphone);
                 } else if (number.equals(mVoiceMailNumber)) {
diff --git a/tests/src/com/android/contacts/RecentCallsListActivityTests.java b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
new file mode 100644
index 0000000..86bd338
--- /dev/null
+++ b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.CallLog.Calls;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import com.android.contacts.RecentCallsListActivity;
+import com.android.internal.telephony.CallerInfo;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Random;
+
+/**
+ * Tests for the contact call list activity.
+ *
+ * Running all tests:
+ *
+ *   runtest contacts
+ * or
+ *   adb shell am instrument -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+
+public class RecentCallsListActivityTests extends ActivityInstrumentationTestCase2<RecentCallsListActivity> {
+    static private final String TAG = "RecentCallsListActivityTests";
+    static private final String[] CALL_LOG_PROJECTION = new String[] {
+            Calls._ID,
+            Calls.NUMBER,
+            Calls.DATE,
+            Calls.DURATION,
+            Calls.TYPE,
+            Calls.CACHED_NAME,
+            Calls.CACHED_NUMBER_TYPE,
+            Calls.CACHED_NUMBER_LABEL
+    };
+    static private final int RAND_DURATION = -1;
+    static private final long NOW = -1L;
+
+    // We get the call list activity and assign is a frame to build
+    // its list.  mAdapter is an inner class of
+    // RecentCallsListActivity to build the rows (view) in the call
+    // list. We reuse it with our own in-mem DB.
+    private RecentCallsListActivity mActivity;
+    private FrameLayout mParentView;
+    private RecentCallsListActivity.RecentCallsAdapter mAdapter;
+    private String mVoicemail;
+
+    // In memory array to hold the rows corresponding to the 'calls' table.
+    private MatrixCursor mCursor;
+    private int mIndex;  // Of the next row.
+
+    private Random mRnd;
+
+    // References to the icons bitmaps used to build the list are stored in a
+    // map mIcons. The keys to retrieve the icons are:
+    // Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE and Calls.MISSED_TYPE.
+    private HashMap<Integer, Bitmap> mCallTypeIcons;
+
+    // An item in the call list. All the methods performing checks use it.
+    private RecentCallsListActivity.RecentCallsListItemViews mItem;
+    // The list of views representing the data in the DB. View are in
+    // reverse order compare to the DB.
+    private View[] mList;
+
+    public RecentCallsListActivityTests() {
+        super("com.android.contacts", RecentCallsListActivity.class);
+        mIndex = 1;
+        mRnd = new Random();
+    }
+
+    @Override
+    public void setUp() {
+        mActivity = (RecentCallsListActivity) getActivity();
+        mVoicemail = mActivity.mVoiceMailNumber;
+        mAdapter = mActivity.mAdapter;
+        mParentView = new FrameLayout(mActivity);
+        mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
+        buildIconMap();
+    }
+
+    /**
+     * Checks that the call icon is not visible for private and
+     * unknown numbers.
+     * Use 2 passes, one where new views are created and one where
+     * half of the total views are updated and the other half created.
+     */
+    public void testCallViewIsNotVisibleForPrivateAndUnknownNumbers() {
+        final int SIZE = 100;
+        mList = new View[SIZE];
+
+        // Insert the first batch of entries.
+        mCursor.moveToFirst();
+        insertRandomEntries(SIZE / 2);
+        int startOfSecondBatch = mCursor.getPosition();
+
+        buildViewListFromDb();
+        checkCallStatus();
+
+        // Append the rest of the entries. We keep the first set of
+        // views around so they get updated and not built from
+        // scratch, this exposes some bugs that are not there when the
+        // call log is launched for the 1st time but show up when the
+        // call log gets updated afterwards.
+        mCursor.move(startOfSecondBatch);
+        insertRandomEntries(SIZE / 2);
+
+        buildViewListFromDb();
+        checkCallStatus();
+    }
+
+    //
+    // HELPERS to check conditions on the DB/views
+    //
+    /**
+     * Check the date of the current list item.
+     * @param date That should be present in the call log list
+     *             item. Only NOW is supported.
+     */
+    private void checkDate(long date) {
+        if (NOW == date) {
+            assertEquals("0 mins ago", mItem.dateView.getText());
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Checks the right icon is used to represent the call type
+     * (missed, incoming, outgoing.) in the current item.
+     */
+    private void checkCallType(int type) {
+        Bitmap icon = ((BitmapDrawable) mItem.iconView.getDrawable()).getBitmap();
+        assertEquals(mCallTypeIcons.get(type), icon);
+    }
+
+    /**
+     * Go over all the views in the list and check that the Call
+     * icon's visibility matches the nature of the number.
+     */
+    private void checkCallStatus() {
+        for (int i = 0; i < mList.length; i++) {
+            if (null == mList[i]) {
+                break;
+            }
+            mItem = (RecentCallsListActivity.RecentCallsListItemViews) mList[i].getTag();
+
+            // callView tag is the phone number.
+            String number = (String) mItem.callView.getTag();
+
+            if (CallerInfo.PRIVATE_NUMBER.equals(number) ||
+                CallerInfo.UNKNOWN_NUMBER.equals(number)) {
+                assertFalse(View.VISIBLE == mItem.callView.getVisibility());
+            } else {
+                assertEquals(View.VISIBLE, mItem.callView.getVisibility());
+            }
+        }
+    }
+
+
+    //
+    // HELPERS to setup the tests.
+    //
+
+    /**
+     * Get the Bitmap from the icons in the contacts package.
+     */
+    private Bitmap getBitmap(String resName) {
+        Resources r = mActivity.getResources();
+        int resid = r.getIdentifier(resName, "drawable", "com.android.contacts");
+        BitmapDrawable d = (BitmapDrawable) r.getDrawable(resid);
+        assertNotNull(d);
+        return d.getBitmap();
+    }
+
+    /**
+     * Fetch all the icons we need in tests from the contacts app and store them in a map.
+     */
+    private void buildIconMap() {
+        mCallTypeIcons = new HashMap<Integer, Bitmap>(3);
+
+        mCallTypeIcons.put(Calls.INCOMING_TYPE, getBitmap("ic_call_log_list_incoming_call"));
+        mCallTypeIcons.put(Calls.MISSED_TYPE, getBitmap("ic_call_log_list_missed_call"));
+        mCallTypeIcons.put(Calls.OUTGOING_TYPE, getBitmap("ic_call_log_list_outgoing_call"));
+    }
+
+    //
+    // HELPERS to build/update the call entries (views) from the DB.
+    //
+
+    /**
+     * Read the DB and foreach call either update the existing view if
+     * one exists already otherwise create one.
+     * The list is build from a DESC view of the DB (last inserted entry is first).
+     */
+    private void buildViewListFromDb() {
+        int i = 0;
+        mCursor.moveToLast();
+        while(!mCursor.isBeforeFirst()) {
+            if (null == mList[i]) {
+                mList[i] = mAdapter.newView(mActivity, mCursor, mParentView);
+            }
+            mAdapter.bindView(mList[i], mActivity, mCursor);
+            mCursor.moveToPrevious();
+            i++;
+        }
+    }
+
+    //
+    // HELPERS to insert numbers in the call log DB.
+    //
+
+    /**
+     * Insert a certain number of random numbers in the DB. Makes sure
+     * there is at least one private and one unknown number in the DB.
+     * @param num Of entries to be inserted.
+     */
+    private void insertRandomEntries(int num) {
+        if (num < 10) {
+            throw new IllegalArgumentException("num should be >= 10");
+        }
+        boolean privateOrUnknownOrVm[];
+        privateOrUnknownOrVm = insertRandomRange(0, num - 2);
+
+        if (privateOrUnknownOrVm[0] && privateOrUnknownOrVm[1]) {
+            insertRandomRange(num - 2, num);
+        } else {
+            insertPrivate(NOW, RAND_DURATION);
+            insertUnknown(NOW, RAND_DURATION);
+        }
+    }
+
+    /**
+     * Insert a new call entry in the test DB.
+     * @param number The phone number. For unknown and private numbers,
+     *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+     * @param date In millisec since epoch. Use NOW to use the current time.
+     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+     * @param type Eigher Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
+     */
+    private void insert(String number, long date, int duration, int type) {
+        MatrixCursor.RowBuilder row = mCursor.newRow();
+        row.add(mIndex);
+        mIndex ++;
+        row.add(number);
+        if (NOW == date) {
+            row.add(new Date().getTime());
+        }
+        if (duration < 0) {
+            duration = mRnd.nextInt(10 * 60);  // 0 - 10 minutes random.
+        }
+        row.add(duration);  // duration
+        if (mVoicemail.equals(number)) {
+            assertEquals(Calls.OUTGOING_TYPE, type);
+        }
+        row.add(type);  // type
+        row.add("");    // cached name
+        row.add(0);     // cached number type
+        row.add("");    // cached number label
+    }
+
+    /**
+     * Insert a new private call entry in the test DB.
+     * @param date In millisec since epoch. Use NOW to use the current time.
+     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+     */
+    private void insertPrivate(long date, int duration) {
+        insert(CallerInfo.PRIVATE_NUMBER, date, duration, Calls.INCOMING_TYPE);
+    }
+
+    /**
+     * Insert a new unknown call entry in the test DB.
+     * @param date In millisec since epoch. Use NOW to use the current time.
+     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+     */
+    private void insertUnknown(long date, int duration) {
+        insert(CallerInfo.UNKNOWN_NUMBER, date, duration, Calls.INCOMING_TYPE);
+    }
+
+    /**
+     * Insert a new voicemail call entry in the test DB.
+     * @param date In millisec since epoch. Use NOW to use the current time.
+     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+     */
+    private void insertVoicemail(long date, int duration) {
+        insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
+    }
+
+    /**
+     * Insert a range [start, end) of random numbers in the DB. For
+     * each row, there is a 1/10 probability that the number will be
+     * marked as PRIVATE or UNKNOWN or VOICEMAIL. For regular numbers, a number is
+     * inserted, its last 4 digits will be the number of the iteration
+     * in the range.
+     * @param start Of the range.
+     * @param end Of the range (excluded).
+     * @return An array with 2 booleans [0 = private number, 1 =
+     * unknown number, 2 = voicemail] to indicate if at least one
+     * private or unknown or voicemail number has been inserted. Since
+     * the numbers are random some tests may want to enforce the
+     * insertion of such numbers.
+     */
+    // TODO: Should insert numbers with contact entries too.
+    private boolean[] insertRandomRange(int start, int end) {
+        boolean[] privateOrUnknownOrVm = new boolean[] {false, false, false};
+
+        for (int i = start; i < end; i++ ) {
+            int type = mRnd.nextInt(10);
+
+            if (0 == type) {
+                insertPrivate(NOW, RAND_DURATION);
+                privateOrUnknownOrVm[0] = true;
+            } else if (1 == type) {
+                insertUnknown(NOW, RAND_DURATION);
+                privateOrUnknownOrVm[1] = true;
+            } else if (2 == type) {
+                insertVoicemail(NOW, RAND_DURATION);
+                privateOrUnknownOrVm[2] = true;
+            } else {
+                int inout = mRnd.nextBoolean() ? Calls.OUTGOING_TYPE :  Calls.INCOMING_TYPE;
+                String number = new Formatter().format("1800123%04d", i).toString();
+                insert(number, NOW, RAND_DURATION, inout);
+            }
+        }
+        return privateOrUnknownOrVm;
+    }
+}