Merge "Contacts NFC sharing: address review comments."
diff --git a/res/layout-xlarge/account_selector_list_item.xml b/res/layout-xlarge/account_selector_list_item.xml
index 38acfc5..32fd0a5 100644
--- a/res/layout-xlarge/account_selector_list_item.xml
+++ b/res/layout-xlarge/account_selector_list_item.xml
@@ -16,9 +16,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
->
+    android:layout_height="?android:attr/listPreferredItemHeight">
     <ImageView android:id="@android:id/icon"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index 6fe2a50..7930f54 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -16,10 +16,9 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="@dimen/account_selector_min_item_height"
     android:paddingLeft="@dimen/account_selector_horizontal_margin"
-    android:paddingRight="@dimen/account_selector_horizontal_margin"
-    android:minHeight="@dimen/account_selector_min_item_height" >
+    android:paddingRight="@dimen/account_selector_horizontal_margin" >
 
     <TextView android:id="@android:id/text1"
         android:layout_width="match_parent"
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index eff5c41..b3043d4 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -44,7 +44,6 @@
 import android.os.Message;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
@@ -53,7 +52,6 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -154,25 +152,6 @@
         public static ContactInfo EMPTY = new ContactInfo();
     }
 
-    public static final class CallLogListItemViews {
-        public TextView line1View;
-        public TextView labelView;
-        public TextView numberView;
-        public TextView dateView;
-        public ImageView iconView;
-        /** The icon used to place a call to the contact. Only present for non-group entries. */
-        public View callView;
-        /** The icon used to expand and collapse an entry. Only present for group entries. */
-        public ImageView groupIndicator;
-        /**
-         * The text view containing the number of items in the group. Only present for group
-         * entries.
-         */
-        public TextView groupSize;
-        /** The contact photo for the contact. Only present for group and stand alone entries. */
-        public ImageView photoView;
-    }
-
     public static final class CallerInfoQuery {
         public String number;
         public int position;
@@ -195,9 +174,8 @@
         private boolean mFirst;
         private Thread mCallerIdThread;
 
-        private Drawable mDrawableIncoming;
-        private Drawable mDrawableOutgoing;
-        private Drawable mDrawableMissed;
+        /** Instance of helper class for managing views. */
+        private final CallLogListItemHelper mCallLogViewsHelper;
 
         /**
          * Reusable char array buffers.
@@ -254,14 +232,16 @@
             mRequests = new LinkedList<CallerInfoQuery>();
             mPreDrawListener = null;
 
-            mDrawableIncoming = getResources().getDrawable(
+            Drawable drawableIncoming = getResources().getDrawable(
                     R.drawable.ic_call_log_list_incoming_call);
-            mDrawableOutgoing = getResources().getDrawable(
+            Drawable drawableOutgoing = getResources().getDrawable(
                     R.drawable.ic_call_log_list_outgoing_call);
-            mDrawableMissed = getResources().getDrawable(
+            Drawable drawableMissed = getResources().getDrawable(
                     R.drawable.ic_call_log_list_missed_call);
 
             mContactPhotoManager = ContactPhotoManager.getInstance(getActivity());
+            mCallLogViewsHelper = new CallLogListItemHelper(getResources(), mVoiceMailNumber,
+                    drawableIncoming, drawableOutgoing, drawableMissed);
         }
 
         /**
@@ -702,7 +682,7 @@
                 // Format the cached call_log phone number
                 formattedNumber = formatPhoneNumber(number, null, countryIso);
             }
-            // 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.
             if (views.callView != null) {
@@ -710,102 +690,21 @@
             }
 
             if (!TextUtils.isEmpty(name)) {
-                views.line1View.setText(name);
-                views.labelView.setVisibility(View.VISIBLE);
-
-                // "type" and "label" are currently unused for SIP addresses.
-                CharSequence numberLabel = null;
-                if (!PhoneNumberUtils.isUriNumber(number)) {
-                    numberLabel = Phone.getTypeLabel(getResources(), ntype, label);
-                }
-                views.numberView.setVisibility(View.VISIBLE);
-                views.numberView.setText(formattedNumber);
-                if (!TextUtils.isEmpty(numberLabel)) {
-                    views.labelView.setText(numberLabel);
-                    views.labelView.setVisibility(View.VISIBLE);
-
-                    // Zero out the numberView's left margin (see below)
-                    ViewGroup.MarginLayoutParams numberLP =
-                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-                    numberLP.leftMargin = 0;
-                    views.numberView.setLayoutParams(numberLP);
-                } else {
-                    // There's nothing to display in views.labelView, so hide it.
-                    // We can't set it to View.GONE, since it's the anchor for
-                    // numberView in the RelativeLayout, so make it INVISIBLE.
-                    //   Also, we need to manually *subtract* some left margin from
-                    // numberView to compensate for the right margin built in to
-                    // labelView (otherwise the number will be indented by a very
-                    // slight amount).
-                    //   TODO: a cleaner fix would be to contain both the label and
-                    // number inside a LinearLayout, and then set labelView *and*
-                    // its padding to GONE when there's no label to display.
-                    views.labelView.setText(null);
-                    views.labelView.setVisibility(View.INVISIBLE);
-
-                    ViewGroup.MarginLayoutParams labelLP =
-                            (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams();
-                    ViewGroup.MarginLayoutParams numberLP =
-                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-                    // Equivalent to setting android:layout_marginLeft in XML
-                    numberLP.leftMargin = -labelLP.rightMargin;
-                    views.numberView.setLayoutParams(numberLP);
-                }
+                mCallLogViewsHelper.setContactNameLabelAndNumber(views, name, number, ntype, label,
+                        formattedNumber);
             } else {
-                if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
-                    number = getString(R.string.unknown);
-                    if (views.callView != null) {
-                        views.callView.setVisibility(View.INVISIBLE);
-                    }
-                } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
-                    number = getString(R.string.private_num);
-                    if (views.callView != null) {
-                        views.callView.setVisibility(View.INVISIBLE);
-                    }
-                } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
-                    number = getString(R.string.payphone);
-                } else if (PhoneNumberUtils.extractNetworkPortion(number)
-                                .equals(mVoiceMailNumber)) {
-                    number = getString(R.string.voicemail);
-                } else {
-                    // Just a raw number, and no cache, so format it nicely
-                    number = formatPhoneNumber(number, null, countryIso);
-                }
-
-                views.line1View.setText(number);
-                views.numberView.setVisibility(View.GONE);
-                views.labelView.setVisibility(View.GONE);
+                // TODO: Do we need to format the number again? Is formattedNumber already storing
+                // this value?
+                mCallLogViewsHelper.setContactNumberOnly(views, number,
+                        formatPhoneNumber(number, null, countryIso));
             }
-
+            mCallLogViewsHelper.setDate(views, c.getLong(CallLogQuery.DATE),
+                    System.currentTimeMillis());
+            mCallLogViewsHelper.setCallType(views, c.getInt(CallLogQuery.CALL_TYPE));
             if (views.photoView != null) {
                 mContactPhotoManager.loadPhoto(views.photoView, photoId);
             }
 
-            long date = c.getLong(CallLogQuery.DATE);
-
-            // Set the date/time field by mixing relative and absolute times.
-            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
-
-            views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
-                    System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
-
-            if (views.iconView != null) {
-                int type = c.getInt(CallLogQuery.CALL_TYPE);
-                // Set the icon
-                switch (type) {
-                    case Calls.INCOMING_TYPE:
-                        views.iconView.setImageDrawable(mDrawableIncoming);
-                        break;
-
-                    case Calls.OUTGOING_TYPE:
-                        views.iconView.setImageDrawable(mDrawableOutgoing);
-                        break;
-
-                    case Calls.MISSED_TYPE:
-                        views.iconView.setImageDrawable(mDrawableMissed);
-                        break;
-                }
-            }
 
             // Listen for the first draw
             if (mPreDrawListener == null) {
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
new file mode 100644
index 0000000..7697872
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogListItemHelper.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2011 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.calllog;
+
+import com.android.contacts.R;
+import com.android.internal.telephony.CallerInfo;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Helper class to fill in the views of a call log entry.
+ */
+/*package*/ class CallLogListItemHelper {
+    /** The resources used to look up strings. */
+    private final Resources mResources;
+    /** The voicemail number. */
+    private final String mVoiceMailNumber;
+    /** Icon for incoming calls. */
+    private final Drawable mDrawableIncoming;
+    /** Icon for outgoing calls. */
+    private final Drawable mDrawableOutgoing;
+    /** Icon for missed calls. */
+    private final Drawable mDrawableMissed;
+
+    /**
+     * Creates a new helper instance.
+     *
+     * @param resources used to look up strings
+     * @param voicemailNumber the voicemail number, used to determine if a call was to voicemail
+     * @param drawableIncoming the icon drawn besides an incoming call entry
+     * @param drawableOutgoing the icon drawn besides an outgoing call entry
+     * @param drawableMissed the icon drawn besides a missed call entry
+     */
+    public CallLogListItemHelper(Resources resources, String voicemailNumber,
+            Drawable drawableIncoming, Drawable drawableOutgoing, Drawable drawableMissed) {
+        mResources = resources;
+        mVoiceMailNumber = voicemailNumber;
+        mDrawableIncoming = drawableIncoming;
+        mDrawableOutgoing = drawableOutgoing;
+        mDrawableMissed = drawableMissed;
+    }
+
+    /**
+     * Sets the name, label, and number for a contact.
+     *
+     * @param views the views to populate
+     * @param name the name of the contact
+     * @param number the number of the contact
+     * @param numberType the type of the number as it appears in the contact, e.g.,
+     *        {@link Phone#TYPE_HOME}
+     * @param label the label of the number, only used if numberType is {@link Phone#TYPE_CUSTOM}
+     * @param formattedNumber the formatted version of the number above
+     */
+    public void setContactNameLabelAndNumber(CallLogListItemViews views, String name, String number,
+            int numberType, String label, String formattedNumber) {
+        views.line1View.setText(name);
+        views.labelView.setVisibility(View.VISIBLE);
+
+        // "type" and "label" are currently unused for SIP addresses.
+        CharSequence numberLabel = null;
+        if (!PhoneNumberUtils.isUriNumber(number)) {
+            numberLabel = Phone.getTypeLabel(mResources, numberType, label);
+        }
+        views.numberView.setVisibility(View.VISIBLE);
+        views.numberView.setText(formattedNumber);
+        if (!TextUtils.isEmpty(numberLabel)) {
+            views.labelView.setText(numberLabel);
+            views.labelView.setVisibility(View.VISIBLE);
+
+            // Zero out the numberView's left margin (see below)
+            ViewGroup.MarginLayoutParams numberLP =
+                    (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
+            numberLP.leftMargin = 0;
+            views.numberView.setLayoutParams(numberLP);
+        } else {
+            // There's nothing to display in views.labelView, so hide it.
+            // We can't set it to View.GONE, since it's the anchor for
+            // numberView in the RelativeLayout, so make it INVISIBLE.
+            //   Also, we need to manually *subtract* some left margin from
+            // numberView to compensate for the right margin built in to
+            // labelView (otherwise the number will be indented by a very
+            // slight amount).
+            //   TODO: a cleaner fix would be to contain both the label and
+            // number inside a LinearLayout, and then set labelView *and*
+            // its padding to GONE when there's no label to display.
+            views.labelView.setText(null);
+            views.labelView.setVisibility(View.INVISIBLE);
+
+            ViewGroup.MarginLayoutParams labelLP =
+                    (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams();
+            ViewGroup.MarginLayoutParams numberLP =
+                    (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
+            // Equivalent to setting android:layout_marginLeft in XML
+            numberLP.leftMargin = -labelLP.rightMargin;
+            views.numberView.setLayoutParams(numberLP);
+        }
+    }
+
+    /**
+     * Sets the number in a call log entry.
+     * <p>
+     * To be used if we do not have a contact with this number.
+     *
+     * @param views the views to populate
+     * @param number the number of the contact
+     * @param formattedNumber the formatted version of the number above
+     */
+    public void setContactNumberOnly(final CallLogListItemViews views, String number,
+            String formattedNumber) {
+        if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
+            number = mResources.getString(R.string.unknown);
+            if (views.callView != null) {
+                views.callView.setVisibility(View.INVISIBLE);
+            }
+        } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
+            number = mResources.getString(R.string.private_num);
+            if (views.callView != null) {
+                views.callView.setVisibility(View.INVISIBLE);
+            }
+        } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
+            number = mResources.getString(R.string.payphone);
+            if (views.callView != null) {
+                views.callView.setVisibility(View.INVISIBLE);
+            }
+        } else if (PhoneNumberUtils.extractNetworkPortion(number)
+                        .equals(mVoiceMailNumber)) {
+            number = mResources.getString(R.string.voicemail);
+        } else {
+            // Just a phone number, so use the formatted version of the number.
+            number = formattedNumber;
+        }
+
+        views.line1View.setText(number);
+        views.numberView.setVisibility(View.GONE);
+        views.labelView.setVisibility(View.GONE);
+    }
+
+    /**
+     * Sets the date in the views.
+     *
+     * @param views the views to populate
+     * @param date the date of the call log entry
+     * @param now the current time relative to which the date should be formatted
+     */
+    public void setDate(final CallLogListItemViews views, long date, long now) {
+        views.dateView.setText(
+                DateUtils.getRelativeTimeSpanString(
+                        date, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE));
+    }
+
+    /**
+     * Sets the type of the call in the views.
+     *
+     * @param views the views to populate
+     * @param type the type of call log entry, e.g., {@link Calls#INCOMING_TYPE}
+     */
+    public void setCallType(final CallLogListItemViews views, int type) {
+        if (views.iconView != null) {
+            // Set the icon
+            switch (type) {
+                case Calls.INCOMING_TYPE:
+                    views.iconView.setImageDrawable(mDrawableIncoming);
+                    break;
+
+                case Calls.OUTGOING_TYPE:
+                    views.iconView.setImageDrawable(mDrawableOutgoing);
+                    break;
+
+                case Calls.MISSED_TYPE:
+                    views.iconView.setImageDrawable(mDrawableMissed);
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
new file mode 100644
index 0000000..65b92e2
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 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.calllog;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Simple value object containing the various views within a call log entry.
+ */
+public final class CallLogListItemViews {
+    /** The first line in the call log entry, containing either the name or the number. */
+    public TextView line1View;
+    /** The label associated with the phone number. */
+    public TextView labelView;
+    /**
+     * The number the call was from or to.
+     * <p>
+     * Only filled in if the number is not already in the first line, i.e., {@link #line1View}.
+     */
+    public TextView numberView;
+    /** The date of the call. */
+    public TextView dateView;
+    /** The icon indicating the type of call. */
+    public ImageView iconView;
+    /** The icon used to place a call to the contact. Only present for non-group entries. */
+    public View callView;
+    /** The icon used to expand and collapse an entry. Only present for group entries. */
+    public ImageView groupIndicator;
+    /**
+     * The text view containing the number of items in the group. Only present for group
+     * entries.
+     */
+    public TextView groupSize;
+    /** The contact photo for the contact. Only present for group and stand alone entries. */
+    public ImageView photoView;
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 8cab92a..ba2794d 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -670,7 +670,8 @@
             @Override
             public void onClick(View v) {
                 final ListPopupWindow popup = new ListPopupWindow(mContext, null);
-                final AccountsListAdapter adapter = new AccountsListAdapter(mContext, true);
+                final AccountsListAdapter adapter =
+                        new AccountsListAdapter(mContext, true, currentAccount);
                 popup.setWidth(anchorView.getWidth());
                 popup.setAnchorView(anchorView);
                 popup.setAdapter(adapter);
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index 97a9f84..1a8b3ea 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -17,8 +17,8 @@
 package com.android.contacts.util;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 
 import android.accounts.Account;
 import android.content.Context;
@@ -29,6 +29,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -41,9 +42,25 @@
     private final Context mContext;
 
     public AccountsListAdapter(Context context, boolean writableOnly) {
+        this(context, writableOnly, null);
+    }
+
+    /**
+     * @param currentAccount the Account currently selected by the user, which should come
+     * first in the list. Can be null.
+     */
+    public AccountsListAdapter(Context context, boolean writableOnly,
+            Account currentAccount) {
         mContext = context;
         mAccountTypes = AccountTypeManager.getInstance(context);
-        mAccounts = mAccountTypes.getAccounts(writableOnly);
+        // We don't want possible side-effect toward AccountTypeManager
+        mAccounts = new ArrayList<Account>(mAccountTypes.getAccounts(writableOnly));
+        if (currentAccount != null
+                && !mAccounts.isEmpty()
+                && !mAccounts.get(0).equals(currentAccount)
+                && mAccounts.remove(currentAccount)) {
+            mAccounts.add(0, currentAccount);
+        }
         mInflater = LayoutInflater.from(context);
     }
 
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 1dbd56f..8e08280 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -18,7 +18,7 @@
 
 import com.android.contacts.R;
 import com.android.contacts.calllog.CallLogFragment;
-import com.android.contacts.calllog.CallLogFragment.CallLogListItemViews;
+import com.android.contacts.calllog.CallLogListItemViews;
 import com.android.internal.telephony.CallerInfo;
 
 import android.content.res.Resources;
@@ -93,7 +93,7 @@
     private HashMap<Integer, Bitmap> mCallTypeIcons;
 
     // An item in the call list. All the methods performing checks use it.
-    private CallLogFragment.CallLogListItemViews mItem;
+    private CallLogListItemViews mItem;
     // The list of views representing the data in the DB. View are in
     // reverse order compare to the DB.
     private View[] mList;
@@ -300,7 +300,7 @@
             if (null == mList[i]) {
                 break;
             }
-            mItem = (CallLogFragment.CallLogListItemViews) mList[i].getTag();
+            mItem = (CallLogListItemViews) mList[i].getTag();
 
             // callView tag is the phone number.
             String number = (String) mItem.callView.getTag();
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
new file mode 100644
index 0000000..958ece5
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 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.calllog;
+
+import com.android.contacts.R;
+import com.android.contacts.util.LocaleTestUtils;
+import com.android.internal.telephony.CallerInfo;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.provider.CallLog.Calls;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link CallLogListItemHelper}.
+ */
+public class CallLogListItemHelperTest extends AndroidTestCase {
+    /** A test voicemail number. */
+    private static final String TEST_VOICEMAIL_NUMBER = "123";
+    /** A drawable to be used for incoming calls. */
+    private static final Drawable TEST_INCOMING_DRAWABLE = new ColorDrawable(Color.BLACK);
+    /** A drawable to be used for outgoing calls. */
+    private static final Drawable TEST_OUTGOING_DRAWABLE = new ColorDrawable(Color.BLUE);
+    /** A drawable to be used for missed calls. */
+    private static final Drawable TEST_MISSED_DRAWABLE = new ColorDrawable(Color.RED);
+
+    /** The object under test. */
+    private CallLogListItemHelper mHelper;
+
+    /** The views used in the tests. */
+    private CallLogListItemViews mViews;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Context context = getContext();
+        mHelper = new CallLogListItemHelper(context.getResources(), TEST_VOICEMAIL_NUMBER,
+                TEST_INCOMING_DRAWABLE, TEST_OUTGOING_DRAWABLE, TEST_MISSED_DRAWABLE);
+        mViews = new CallLogListItemViews();
+        // Only set the views that are needed in the tests.
+        mViews.iconView = new ImageView(context);
+        mViews.dateView = new TextView(context);
+        mViews.callView = new View(context);
+        mViews.line1View = new TextView(context);
+        mViews.labelView = new TextView(context);
+        mViews.numberView = new TextView(context);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mHelper = null;
+        mViews = null;
+        super.tearDown();
+    }
+
+    public void testSetContactNumberOnly() {
+        mHelper.setContactNumberOnly(mViews, "12125551234", "1-212-555-1234");
+        assertEquals("1-212-555-1234", mViews.line1View.getText());
+        assertEquals(View.GONE, mViews.labelView.getVisibility());
+        assertEquals(View.GONE, mViews.numberView.getVisibility());
+        assertEquals(View.VISIBLE, mViews.callView.getVisibility());
+    }
+
+    public void testSetContactNumberOnly_Unknown() {
+        mHelper.setContactNumberOnly(mViews, CallerInfo.UNKNOWN_NUMBER, "");
+        assertEquals(getContext().getString(R.string.unknown), mViews.line1View.getText());
+        assertEquals(View.GONE, mViews.labelView.getVisibility());
+        assertEquals(View.GONE, mViews.numberView.getVisibility());
+        assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
+    }
+
+    public void testSetContactNumberOnly_Private() {
+        mHelper.setContactNumberOnly(mViews, CallerInfo.PRIVATE_NUMBER, "");
+        assertEquals(getContext().getString(R.string.private_num), mViews.line1View.getText());
+        assertEquals(View.GONE, mViews.labelView.getVisibility());
+        assertEquals(View.GONE, mViews.numberView.getVisibility());
+        assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
+    }
+
+    public void testSetContactNumberOnly_Payphone() {
+        mHelper.setContactNumberOnly(mViews, CallerInfo.PAYPHONE_NUMBER, "");
+        assertEquals(getContext().getString(R.string.payphone), mViews.line1View.getText());
+        assertEquals(View.GONE, mViews.labelView.getVisibility());
+        assertEquals(View.GONE, mViews.numberView.getVisibility());
+        assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
+    }
+
+    public void testSetContactNumberOnly_Voicemail() {
+        mHelper.setContactNumberOnly(mViews, TEST_VOICEMAIL_NUMBER, "");
+        assertEquals(getContext().getString(R.string.voicemail), mViews.line1View.getText());
+        assertEquals(View.GONE, mViews.labelView.getVisibility());
+        assertEquals(View.GONE, mViews.numberView.getVisibility());
+        assertEquals(View.VISIBLE, mViews.callView.getVisibility());
+    }
+
+    public void testSetDate() {
+        // This test requires the locale to be set to US.
+        LocaleTestUtils localeTestUtils = new LocaleTestUtils(getContext());
+        localeTestUtils.setLocale(Locale.US);
+        try {
+            mHelper.setDate(mViews,
+                    new GregorianCalendar(2011, 5, 1, 12, 0, 0).getTimeInMillis(),
+                    new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
+            assertEquals("1 hour ago", mViews.dateView.getText());
+            mHelper.setDate(mViews,
+                    new GregorianCalendar(2010, 5, 1, 12, 0, 0).getTimeInMillis(),
+                    new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
+            assertEquals("June 1, 2010", mViews.dateView.getText());
+        } finally {
+            localeTestUtils.restoreLocale();
+        }
+    }
+
+    public void testSetCallType_Icon() {
+        mHelper.setCallType(mViews, Calls.INCOMING_TYPE);
+        assertEquals(TEST_INCOMING_DRAWABLE, mViews.iconView.getDrawable());
+        mHelper.setCallType(mViews, Calls.OUTGOING_TYPE);
+        assertEquals(TEST_OUTGOING_DRAWABLE, mViews.iconView.getDrawable());
+        mHelper.setCallType(mViews, Calls.MISSED_TYPE);
+        assertEquals(TEST_MISSED_DRAWABLE, mViews.iconView.getDrawable());
+    }
+}
diff --git a/tests/src/com/android/contacts/util/LocaleTestUtils.java b/tests/src/com/android/contacts/util/LocaleTestUtils.java
new file mode 100644
index 0000000..e0a8670
--- /dev/null
+++ b/tests/src/com/android/contacts/util/LocaleTestUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 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.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import java.util.Locale;
+
+/**
+ * Utility class to save and restore the locale of the system.
+ * <p>
+ * This can be used for tests that assume to be run in a certain locale, e.g., because they
+ * check against strings in a particular language or require an assumption on how the system
+ * will behave in a specific locale.
+ * <p>
+ * In your test, you can change the locale with the following code:
+ * <pre>
+ * public class CanadaFrenchTest extends AndroidTestCase {
+ *     private LocaleTestUtils mLocaleTestUtils;
+ *
+ *     &#64;Override
+ *     public void setUp() throws Exception {
+ *         super.setUp();
+ *         mLocaleTestUtils = new LocaleTestUtils(getContext());
+ *         mLocaleTestUtils.setLocale(Locale.CANADA_FRENCH);
+ *     }
+ *
+ *     &#64;Override
+ *     public void tearDown() throws Exception {
+ *         mLocaleTestUtils.restoreLocale();
+ *         mLocaleTestUtils = null;
+ *         super.tearDown();
+ *     }
+ *
+ *     ...
+ * }
+ * </pre>
+ * Note that one should not call {@link #setLocale(Locale)} more than once without calling
+ * {@link #restoreLocale()} first.
+ * <p>
+ * This class is not thread-safe. Usually its methods should be invoked only from the test thread.
+ */
+public class LocaleTestUtils {
+    private final Context mContext;
+    private boolean mSaved;
+    private Locale mSavedContextLocale;
+    private Locale mSavedSystemLocale;
+
+    /**
+     * Create a new instance that can be used to set and reset the locale for the given context.
+     *
+     * @param context the context on which to alter the locale
+     */
+    public LocaleTestUtils(Context context) {
+        mContext = context;
+        mSaved = false;
+    }
+
+    /**
+     * Set the locale to the given value and saves the previous value.
+     *
+     * @param locale the value to which the locale should be set
+     * @throws IllegalStateException if the locale was already set
+     */
+    public void setLocale(Locale locale) {
+        if (mSaved) {
+            throw new IllegalStateException(
+                    "call restoreLocale() before calling setLocale() again");
+        }
+        mSavedContextLocale = setResourcesLocale(mContext.getResources(), locale);
+        mSavedSystemLocale = setResourcesLocale(Resources.getSystem(), locale);
+        mSaved = true;
+    }
+
+    /**
+     * Restores the previously set locale.
+     *
+     * @throws IllegalStateException if the locale was not set using {@link #setLocale(Locale)}
+     */
+    public void restoreLocale() {
+        if (!mSaved) {
+            throw new IllegalStateException("call setLocale() before calling restoreLocale()");
+        }
+        setResourcesLocale(mContext.getResources(), mSavedContextLocale);
+        setResourcesLocale(Resources.getSystem(), mSavedSystemLocale);
+        mSaved = false;
+    }
+
+    /**
+     * Sets the locale for the given resources and returns the previous locale.
+     *
+     * @param resources the resources on which to set the locale
+     * @param locale the value to which to set the locale
+     * @return the previous value of the locale for the resources
+     */
+    private Locale setResourcesLocale(Resources resources, Locale locale) {
+        Configuration contextConfiguration = new Configuration(resources.getConfiguration());
+        Locale savedLocale = contextConfiguration.locale;
+        contextConfiguration.locale = locale;
+        resources.updateConfiguration(contextConfiguration, null);
+        return savedLocale;
+    }
+}
\ No newline at end of file