Merge "Show group headers correctly"
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index 157c761..df1ec62 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -163,12 +163,14 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:textAppearance="?android:attr/textAppearanceMedium"
+                        android:textColor="?attr/call_log_primary_text_color"
                     />
 
                     <TextView android:id="@+id/call_and_sms_label"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:textAppearance="?android:attr/textAppearanceSmall"
+                        android:textColor="?attr/call_log_primary_text_color"
                         android:textAllCaps="true"
                     />
 
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
index 7cfcef3..26689be 100644
--- a/res/layout/call_log_fragment.xml
+++ b/res/layout/call_log_fragment.xml
@@ -39,6 +39,7 @@
             android:layout_height="match_parent"
             android:fadingEdge="none"
             android:scrollbarStyle="outsideOverlay"
+            android:divider="@null"
         />
         <TextView android:id="@android:id/empty"
             android:layout_width="match_parent"
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 734e1b8..3458dd9 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -19,6 +19,7 @@
     class="com.android.contacts.calllog.CallLogListItemView"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:orientation="vertical"
 >
     <!--
         This layout may represent either a call log item or one of the
@@ -170,4 +171,13 @@
             android:layout_height="2dip"
             android:background="@color/call_log_voicemail_highlight_color"/>
     </LinearLayout>
+
+    <View
+        android:id="@+id/call_log_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1px"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:background="#1a1a1a"
+    />
 </view>
diff --git a/res/layout/stream_item_row_image_and_text.xml b/res/layout/stream_item_row_image_and_text.xml
index 3642592..882a23b 100644
--- a/res/layout/stream_item_row_image_and_text.xml
+++ b/res/layout/stream_item_row_image_and_text.xml
@@ -57,10 +57,13 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
                 <TextView android:id="@+id/stream_item_attribution"
-                    android:layout_width="wrap_content"
+                    android:layout_width="0dip"
                     android:layout_height="wrap_content"
                     android:textAppearance="?android:attr/textAppearanceSmall"
-                    android:textColor="?android:attr/textColorSecondary" />
+                    android:textColor="?android:attr/textColorSecondary"
+                    android:maxLines="1"
+                    android:ellipsize="end"
+                    android:layout_weight="1" />
 
                 <TextView android:id="@+id/stream_item_comments"
                     android:layout_width="wrap_content"
@@ -68,7 +71,8 @@
                     android:layout_marginLeft="@dimen/detail_update_section_attribution_comments_padding"
                     android:textAppearance="?android:attr/textAppearanceSmall"
                     android:textColor="?android:attr/textColorSecondary"
-                    android:visibility="gone" />
+                    android:visibility="gone"
+                    android:maxLines="1" />
             </LinearLayout>
 
         </LinearLayout>
diff --git a/res/layout/stream_item_row_text_only.xml b/res/layout/stream_item_row_text_only.xml
index 7b1f255..919ee52 100644
--- a/res/layout/stream_item_row_text_only.xml
+++ b/res/layout/stream_item_row_text_only.xml
@@ -29,17 +29,21 @@
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
+
         <TextView android:id="@+id/stream_item_attribution"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary" />
-
+            android:textColor="?android:attr/textColorSecondary"
+            android:ellipsize="end"
+            android:maxLines="1" />
         <TextView android:id="@+id/stream_item_comments"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="@dimen/detail_update_section_attribution_comments_padding"
             android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary" />
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="1"/>
+
     </LinearLayout>
 </LinearLayout>
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 0dbb22c..0addb07 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -1264,7 +1264,7 @@
     @Override
     protected void onForceLoad() {
         final LoadContactTask task = new LoadContactTask();
-        task.execute((Void[])null);
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
     }
 
     @Override
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index 0f7065d..fd2e6a2 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -21,6 +21,7 @@
 import com.google.android.collect.Sets;
 
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -642,7 +643,6 @@
                         ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
                         .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                                 String.valueOf(MAX_PHOTOS_TO_PRELOAD))
-                        .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "1")
                         .build();
                 cursor = mResolver.query(uri, new String[] { Contacts.PHOTO_ID },
                         Contacts.PHOTO_ID + " NOT NULL AND " + Contacts.PHOTO_ID + "!=0",
@@ -698,8 +698,7 @@
 
             Cursor cursor = null;
             try {
-                cursor = mResolver.query(Data.CONTENT_URI.buildUpon()
-                        .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "1").build(),
+                cursor = mResolver.query(Data.CONTENT_URI,
                         COLUMNS,
                         mStringBuilder.toString(),
                         mPhotoIdsAsStrings.toArray(EMPTY_STRING_ARRAY),
@@ -719,9 +718,30 @@
                 }
             }
 
-            // Remaining photos were not found in the database - mark the cache accordingly.
+            // Remaining photos were not found in the contacts database (but might be in profile).
             for (Long id : mPhotoIds) {
-                cacheBitmap(id, null, preloading);
+                if (ContactsContract.isProfileId(id)) {
+                    Cursor profileCursor = null;
+                    try {
+                        profileCursor = mResolver.query(
+                                ContentUris.withAppendedId(Data.CONTENT_URI, id),
+                                COLUMNS, null, null, null);
+                        if (profileCursor != null && profileCursor.moveToFirst()) {
+                            cacheBitmap(profileCursor.getLong(0), profileCursor.getBlob(1),
+                                    preloading);
+                        } else {
+                            // Couldn't load a photo this way either.
+                            cacheBitmap(id, null, preloading);
+                        }
+                    } finally {
+                        if (profileCursor != null) {
+                            profileCursor.close();
+                        }
+                    }
+                } else {
+                    // Not a profile photo and not found - mark the cache accordingly
+                    cacheBitmap(id, null, preloading);
+                }
             }
 
             mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 94daf06..604d528 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -541,6 +541,7 @@
         if (section == CallLogQuery.SECTION_NEW_HEADER
                 || section == CallLogQuery.SECTION_OLD_HEADER) {
             views.listItemView.setVisibility(View.GONE);
+            views.bottomDivider.setVisibility(View.GONE);
             views.listHeaderView.setVisibility(View.VISIBLE);
             views.listHeaderTextView.setText(
                     section == CallLogQuery.SECTION_NEW_HEADER
@@ -551,6 +552,7 @@
         }
         // Default case: an item in the call log.
         views.listItemView.setVisibility(View.VISIBLE);
+        views.bottomDivider.setVisibility(isLastOfSection(c) ? View.GONE : View.VISIBLE);
         views.listHeaderView.setVisibility(View.GONE);
 
         final String number = c.getString(CallLogQuery.NUMBER);
@@ -654,6 +656,16 @@
         }
     }
 
+    /** Returns true if this is the last item of a section. */
+    private boolean isLastOfSection(Cursor c) {
+        if (c.isLast()) return true;
+        final int section = c.getInt(CallLogQuery.SECTION);
+        if (!c.moveToNext()) return true;
+        final int nextSection = c.getInt(CallLogQuery.SECTION);
+        c.moveToPrevious();
+        return section != nextSection;
+    }
+
     /** Checks whether the contact info from the call log matches the one from the contacts db. */
     private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
         // The call log only contains a subset of the fields in the contacts db.
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index 847e354..938b2e3 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -47,11 +47,13 @@
     public final View listHeaderView;
     /** The text of the header of a section. */
     public final TextView listHeaderTextView;
+    /** The divider to be shown below items. */
+    public final View bottomDivider;
 
     private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
             ImageView secondaryActionView, View unheardView, View dividerView,
             PhoneCallDetailsViews phoneCallDetailsViews, View listItemView,
-            View listHeaderView, TextView listHeaderTextView) {
+            View listHeaderView, TextView listHeaderTextView, View bottomDivider) {
         this.quickContactView = quickContactView;
         this.primaryActionView = primaryActionView;
         this.secondaryActionView = secondaryActionView;
@@ -61,6 +63,7 @@
         this.listItemView = listItemView;
         this.listHeaderView = listHeaderView;
         this.listHeaderTextView = listHeaderTextView;
+        this.bottomDivider = bottomDivider;
     }
 
     public static CallLogListItemViews fromView(View view) {
@@ -73,7 +76,8 @@
                 PhoneCallDetailsViews.fromView(view),
                 view.findViewById(R.id.call_log_item),
                 view.findViewById(R.id.call_log_header),
-                (TextView) view.findViewById(R.id.call_log_header_text));
+                (TextView) view.findViewById(R.id.call_log_header_text),
+                view.findViewById(R.id.call_log_divider));
     }
 
     public static CallLogListItemViews createForTest(Context context) {
@@ -86,6 +90,7 @@
                 PhoneCallDetailsViews.createForTest(context),
                 new View(context),
                 new View(context),
-                new TextView(context));
+                new TextView(context),
+                new View(context));
     }
 }
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index e3e8875..1ff16d2 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -21,6 +21,7 @@
 import com.android.contacts.SpecialCharSequenceMgr;
 import com.android.contacts.activities.DialtactsActivity;
 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
+import com.android.contacts.util.PhoneNumberFormatter;
 import com.android.internal.telephony.ITelephony;
 import com.android.phone.CallLogAsync;
 import com.android.phone.HapticFeedback;
@@ -150,33 +151,6 @@
 
     private String mCurrentCountryIso;
 
-    /**
-     * May be null for a moment and filled by AsyncTask. Must not be touched outside UI thread.
-     */
-    private PhoneNumberFormattingTextWatcher mTextWatcher;
-
-    /**
-     * Delays {@link PhoneNumberFormattingTextWatcher} creation as it may cause disk read operation.
-     */
-    private final AsyncTask<Void, Void, Void> mTextWatcherLoadAsyncTask =
-            new AsyncTask<Void, Void, Void>() {
-
-        private PhoneNumberFormattingTextWatcher mTemporaryWatcher;
-
-        @Override
-        protected Void doInBackground(Void... params) {
-            mTemporaryWatcher = new PhoneNumberFormattingTextWatcher(mCurrentCountryIso);
-            return null;
-        }
-
-        @Override
-        protected void onPostExecute(Void result) {
-            // Should be in UI thread.
-            mTextWatcher = mTemporaryWatcher;
-            mDigits.addTextChangedListener(mTextWatcher);
-        }
-    };
-
     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         /**
          * Listen for phone state changes so that we can take down the
@@ -261,14 +235,7 @@
         mDigits.setOnKeyListener(this);
         mDigits.addTextChangedListener(this);
 
-        if (mTextWatcher == null) {
-            if (mTextWatcherLoadAsyncTask.getStatus() == AsyncTask.Status.PENDING) {
-                // Start loading text watcher for phone number, which requires disk read.
-                mTextWatcherLoadAsyncTask.execute();
-            }
-        } else {
-            mDigits.addTextChangedListener(mTextWatcher);
-        }
+        PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
 
         // Soft menu button should appear only when there's no hardware menu button.
         final View overflowMenuButton = fragmentView.findViewById(R.id.overflow_menu);
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index a448f4e..a4ad53a 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -23,6 +23,7 @@
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.util.NameConverter;
+import com.android.contacts.util.PhoneNumberFormatter;
 
 import android.content.Context;
 import android.content.Entity;
@@ -185,8 +186,7 @@
             int inputType = field.inputType;
             fieldView.setInputType(inputType);
             if (inputType == InputType.TYPE_CLASS_PHONE) {
-                fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher(
-                        ContactsUtils.getCurrentCountryIso(mContext)));
+                PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(mContext, fieldView);
             }
             fieldView.setMinLines(field.minLines);
 
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index a4163fd..528e246 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -351,7 +351,7 @@
             mAdapter.configureDirectoryLoader(loader);
             return loader;
         } else {
-            CursorLoader loader = new CursorLoader(mContext, null, null, null, null, null);
+            CursorLoader loader = createCursorLoader();
             long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
                     ? args.getLong(DIRECTORY_ID_ARG_KEY)
                     : Directory.DEFAULT;
@@ -360,6 +360,10 @@
         }
     }
 
+    public CursorLoader createCursorLoader() {
+        return new CursorLoader(mContext, null, null, null, null, null);
+    }
+
     private void startLoadingDirectoryPartition(int partitionIndex) {
         DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
         partition.setStatus(DirectoryPartition.STATUS_LOADING);
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index c057a48..0553909 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -144,11 +144,6 @@
                 .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
     }
 
-    protected static Uri includeProfileEntry(Uri uri) {
-        return uri.buildUpon()
-                .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "true").build();
-    }
-
     public boolean getHasPhoneNumber(int position) {
         return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
     }
diff --git a/src/com/android/contacts/list/ContactsSectionIndexer.java b/src/com/android/contacts/list/ContactsSectionIndexer.java
index 109b8ba..5ecb31c 100644
--- a/src/com/android/contacts/list/ContactsSectionIndexer.java
+++ b/src/com/android/contacts/list/ContactsSectionIndexer.java
@@ -26,9 +26,9 @@
  */
 public class ContactsSectionIndexer implements SectionIndexer {
 
-    private final String[] mSections;
-    private final int[] mPositions;
-    private final int mCount;
+    private String[] mSections;
+    private int[] mPositions;
+    private int mCount;
 
     /**
      * Constructor.
@@ -95,8 +95,25 @@
     }
 
     public void setProfileHeader(String header) {
-        if (mSections != null && mSections.length > 0) {
-            mSections[0] = header;
+        if (mSections != null) {
+            // Don't do anything if the header is already set properly.
+            if (mSections.length > 0 && header.equals(mSections[0])) {
+                return;
+            }
+
+            // Since the section indexer isn't aware of the profile at the top, we need to add a
+            // special section at the top for it and shift everything else down.
+            String[] tempSections = new String[mSections.length + 1];
+            int[] tempPositions = new int[mPositions.length + 1];
+            tempSections[0] = header;
+            tempPositions[0] = 0;
+            for (int i = 1; i <= mPositions.length; i++) {
+                tempSections[i] = mSections[i - 1];
+                tempPositions[i] = mPositions[i - 1] + 1;
+            }
+            mSections = tempSections;
+            mPositions = tempPositions;
+            mCount++;
         }
     }
 }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 306e244..9ddc4b7 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -18,6 +18,7 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.ContactEditorFragment;
 
+import android.content.CursorLoader;
 import android.content.Intent;
 import android.database.Cursor;
 import android.provider.ContactsContract.Contacts;
@@ -56,6 +57,11 @@
     }
 
     @Override
+    public CursorLoader createCursorLoader() {
+        return new ProfileAndContactsLoader(getActivity());
+    }
+
+    @Override
     protected void onItemClick(int position, long id) {
         viewContact(getAdapter().getContactUri(position));
     }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 007af6c..9a3f05e 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -57,6 +57,9 @@
 
     @Override
     public void configureLoader(CursorLoader loader, long directoryId) {
+        if (loader instanceof ProfileAndContactsLoader) {
+            ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
+        }
 
         ContactListFilter filter = getFilter();
         if (isSearchMode()) {
@@ -129,11 +132,6 @@
                     .build();
         }
 
-        // Include the user's personal profile.
-        if (shouldIncludeProfile()) {
-            uri = includeProfileEntry(uri);
-        }
-
         loader.setUri(uri);
     }
 
diff --git a/src/com/android/contacts/list/ProfileAndContactsLoader.java b/src/com/android/contacts/list/ProfileAndContactsLoader.java
new file mode 100644
index 0000000..7f85ea6
--- /dev/null
+++ b/src/com/android/contacts/list/ProfileAndContactsLoader.java
@@ -0,0 +1,90 @@
+/*
+ * 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.list;
+
+import com.google.android.collect.Lists;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Profile;
+
+import java.util.List;
+
+/**
+ * A loader for use in the default contact list, which will also query for the user's profile
+ * if configured to do so.
+ */
+public class ProfileAndContactsLoader extends CursorLoader {
+
+    private boolean mLoadProfile;
+    private String[] mProjection;
+
+    public ProfileAndContactsLoader(Context context) {
+        super(context);
+    }
+
+    public void setLoadProfile(boolean flag) {
+        mLoadProfile = flag;
+    }
+
+    public void setProjection(String[] projection) {
+        super.setProjection(projection);
+        mProjection = projection;
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // First load the profile, if enabled.
+        List<Cursor> cursors = Lists.newArrayList();
+        if (mLoadProfile) {
+            cursors.add(loadProfile());
+        }
+        final Cursor contactsCursor = super.loadInBackground();
+        cursors.add(contactsCursor);
+        return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
+            @Override
+            public Bundle getExtras() {
+                // Need to get the extras from the contacts cursor.
+                return contactsCursor.getExtras();
+            }
+        };
+    }
+
+    /**
+     * Loads the profile into a MatrixCursor.
+     */
+    private MatrixCursor loadProfile() {
+        Cursor cursor = getContext().getContentResolver().query(Profile.CONTENT_URI, mProjection,
+                null, null, null);
+        try {
+            MatrixCursor matrix = new MatrixCursor(mProjection);
+            Object[] row = new Object[mProjection.length];
+            while (cursor.moveToNext()) {
+                for (int i = 0; i < row.length; i++) {
+                    row[i] = cursor.getString(i);
+                }
+                matrix.addRow(row);
+            }
+            return matrix;
+        } finally {
+            cursor.close();
+        }
+    }
+}
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 65af3ee..33d6c97 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -45,6 +45,7 @@
 import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.TimingLogger;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -275,7 +276,9 @@
         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
             Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground start");
         }
-        long startTime = SystemClock.currentThreadTimeMillis();
+        TimingLogger timings = new TimingLogger(TAG, "loadAccountsInBackground");
+        final long startTime = SystemClock.currentThreadTimeMillis();
+        final long startTimeWall = SystemClock.elapsedRealtime();
 
         // Account types, keyed off the account type and data set concatenation.
         Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
@@ -371,6 +374,7 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Problem loading accounts: " + e.toString());
         }
+        timings.addSplit("Loaded account types");
 
         // Map in accounts to associate the account names with each account type entry.
         Account[] accounts = mAccountManager.getAccounts();
@@ -402,11 +406,7 @@
         Collections.sort(allAccounts, ACCOUNT_COMPARATOR);
         Collections.sort(writableAccounts, ACCOUNT_COMPARATOR);
 
-        // The UI will need a phone number formatter.  We can preload meta data for the
-        // current locale to prevent a delay later on.
-        PhoneNumberUtil.getInstance().getAsYouTypeFormatter(Locale.getDefault().getCountry());
-
-        long endTime = SystemClock.currentThreadTimeMillis();
+        timings.addSplit("Loaded accounts");
 
         synchronized (this) {
             mAccountTypesWithDataSets = accountTypesByTypeAndDataSet;
@@ -416,8 +416,13 @@
                     mContext, allAccounts, accountTypesByTypeAndDataSet);
         }
 
+        timings.dumpToLog();
+        final long endTimeWall = SystemClock.elapsedRealtime();
+        final long endTime = SystemClock.currentThreadTimeMillis();
+
         Log.i(TAG, "Loaded meta-data for " + mAccountTypesWithDataSets.size() + " account types, "
-                + mAccounts.size() + " accounts in " + (endTime - startTime) + "ms");
+                + mAccounts.size() + " accounts in " + (endTimeWall - startTimeWall) + "ms(wall) "
+                + (endTime - startTime) + "ms(cpu)");
 
         if (mInitializationLatch != null) {
             mInitializationLatch.countDown();
@@ -527,8 +532,7 @@
 
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "Type " + accountTypeWithDataSet
-                        + " inviteClass=" + type.getInviteContactActivityClassName()
-                        + " inviteAction=" + type.getInviteContactActionLabel(context));
+                        + " inviteClass=" + type.getInviteContactActivityClassName());
             }
             if (!TextUtils.isEmpty(type.getInviteContactActivityClassName())) {
                 result.put(accountTypeWithDataSet, type);
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index f22369f..472f701 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -432,7 +432,7 @@
                             if (result == null) return;
                             photoView.setImageBitmap(result);
                         }
-                    }.execute();
+                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
                 }
                 final int photoColumnIndex = cursor.getColumnIndex(Photo.PHOTO);
                 final byte[] photoBlob = cursor.getBlob(photoColumnIndex);
diff --git a/src/com/android/contacts/util/PhoneNumberFormatter.java b/src/com/android/contacts/util/PhoneNumberFormatter.java
new file mode 100644
index 0000000..6e63aac
--- /dev/null
+++ b/src/com/android/contacts/util/PhoneNumberFormatter.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.contacts.ContactsUtils;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.widget.TextView;
+
+public final class PhoneNumberFormatter {
+    private PhoneNumberFormatter() {}
+
+    /**
+     * Load {@link TextWatcherLoadAsyncTask} in a worker thread and set it to a {@link TextView}.
+     */
+    private static class TextWatcherLoadAsyncTask extends
+            AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher> {
+        private final String mCountryCode;
+        private final TextView mTextView;
+
+        public TextWatcherLoadAsyncTask(String countryCode, TextView textView) {
+            mCountryCode = countryCode;
+            mTextView = textView;
+        }
+
+        @Override
+        protected PhoneNumberFormattingTextWatcher doInBackground(Void... params) {
+            return new PhoneNumberFormattingTextWatcher(mCountryCode);
+        }
+
+        @Override
+        protected void onPostExecute(PhoneNumberFormattingTextWatcher watcher) {
+            if (watcher == null || isCancelled()) {
+                return; // May happen if we cancel the task.
+            }
+            if (mTextView.getHandler() == null) {
+                return; // View is already detached.
+            }
+            mTextView.addTextChangedListener(watcher);
+
+            // Note changes the user made before onPostExecute() will not be formatted, but
+            // once they type the next letter we format the entire text, so it's not a big deal.
+            // (And loading PhoneNumberFormattingTextWatcher is usually fast enough.)
+            // We could use watcher.afterTextChanged(mTextView.getEditableText()) to force format
+            // the existing content here, but that could cause unwanted results.
+            // (e.g. the contact editor thinks the user changed the content, and would save
+            // when closed even when the user didn't make other changes.)
+        }
+    }
+
+    /**
+     * Delay-set {@link PhoneNumberFormattingTextWatcher} to a {@link TextView}.
+     */
+    public static final void setPhoneNumberFormattingTextWatcher(Context context,
+            TextView textView) {
+        new TextWatcherLoadAsyncTask(ContactsUtils.getCurrentCountryIso(context), textView)
+                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+    }
+}
diff --git a/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java b/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java
index ef7913d..9e481f8 100644
--- a/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java
+++ b/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java
@@ -25,6 +25,7 @@
 import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
 
 import com.android.contacts.R;
+import com.android.contacts.util.UriUtils;
 
 import android.database.Cursor;
 import android.net.Uri;
@@ -219,9 +220,9 @@
 
         Uri actionUri = null;
         if (action == Action.CALL_VOICEMAIL) {
-            actionUri = Uri.parse(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
+            actionUri = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
         } else if (action == Action.CONFIGURE_VOICEMAIL) {
-            actionUri = Uri.parse(cursor.getString(SETTINGS_URI_INDEX));
+            actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX));
         }
         return new MessageStatusWithPriority(
                 new StatusMessage(sourcePackage, overallState.getCallLogMessageId(),
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
index f624113..4d24f54 100644
--- a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -516,12 +516,13 @@
                 }
                 final Intent intent = new Intent("android.intent.action.VIEW");
                 intent.setData(uri);
-                bindIntentToClass(intent, "CallDetailActivity");
+                bindIntentToClass(intent, "com.android.contacts.CallDetailActivity");
                 startActivity(intent);
                 break;
             }
             case LEGACY_CALL_LOG_ACTIVITY: {
-                startActivity(bindIntentToClass(new Intent(), "activities.CallLogActivity"));
+                startActivity(bindIntentToClass(new Intent(),
+                        "com.android.contacts.activities.CallLogActivity"));
                 break;
             }