Merge change 26281 into eclair

* changes:
  New text field to display the punched numbers.
diff --git a/res/layout-finger/fasttrack_item.xml b/res/layout-finger/fasttrack_item.xml
index e820a03..58d85aa 100644
--- a/res/layout-finger/fasttrack_item.xml
+++ b/res/layout-finger/fasttrack_item.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.contacts.ui.CheckableImageView 
+<com.android.contacts.ui.widget.CheckableImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="59dip"
     android:layout_height="52dip"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4de02ba..f358592 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -887,47 +887,25 @@
     <!-- Toast indicating that sharing a contact has failed. -->
     <string name="share_error">This contact cannot be shared.</string>
 
+    <!-- Header that expands to list all name types when editing a structured name of a contact -->
+    <string name="nameLabelsGroup">Name</string>
+    <!-- Header that expands to list all nickname types when editing a nickname of a contact -->
+    <string name="nicknameLabelsGroup">Nickname</string>
+    <!-- Header that expands to list all organization types when editing an organization of a contact -->
+    <string name="organizationLabelsGroup">Organization</string>
+    <!-- Header that expands to list all website types when editing a website of a contact -->
+    <string name="websiteLabelsGroup">Website</string>
 
-
-<!-- TODO: add comments to each of these strings to prepare for translation -->
-<string name="nameLabelsGroup">Name</string>
-<string name="nicknameLabelsGroup">Nickname</string>
-<string name="organizationLabelsGroup">Organization</string>
-<string name="websiteLabelsGroup">Website</string>
-
-<string name="type_home">Home</string>
-<string name="type_mobile">Mobile</string>
-<string name="type_work">Work</string>
-<string name="type_fax_work">Work Fax</string>
-<string name="type_fax_home">Home Fax</string>
-<string name="type_pager">Pager</string>
-<string name="type_other">Other</string>
-<string name="type_custom">Custom</string>
-
-<!-- Single-character overlays on shortcut icons -->
-<string name="type_short_home">H</string>
-<string name="type_short_mobile">M</string>
-<string name="type_short_work">W</string>
-<string name="type_short_pager">P</string>
-<string name="type_short_other">O</string>
-
-<!-- exchange specific -->
-<string name="type_home_2">Home 2</string>
-<string name="type_work_2">Work 2</string>
-<string name="type_car">Car</string>
-<string name="type_company_main">Company Main</string>
-<string name="type_mms">MMS</string>
-<string name="type_radio">Radio</string>
-<string name="type_assistant">Assistant</string>
-
-<string name="type_im_aim">AIM</string>
-<string name="type_im_msn">Windows Live</string>
-<string name="type_im_yahoo">Yahoo</string>
-<string name="type_im_skype">Skype</string>
-<string name="type_im_qq">QQ</string>
-<string name="type_im_google_talk">Google Talk</string>
-<string name="type_im_icq">ICQ</string>
-<string name="type_im_jabber">Jabber</string>
+    <!-- Single-character overlay for home phone numbers when creating desktop shortcuts -->
+    <string name="type_short_home">H</string>
+    <!-- Single-character overlay for mobile phone numbers when creating desktop shortcuts -->
+    <string name="type_short_mobile">M</string>
+    <!-- Single-character overlay for work phone numbers when creating desktop shortcuts -->
+    <string name="type_short_work">W</string>
+    <!-- Single-character overlay for pager phone numbers when creating desktop shortcuts -->
+    <string name="type_short_pager">P</string>
+    <!-- Single-character overlay for other phone numbers when creating desktop shortcuts -->
+    <string name="type_short_other">O</string>
 
     <!-- Shown as the header title over a collapsible section that, by default, hides
          secondary contact detail edit fields, such as birthday. -->
@@ -957,89 +935,165 @@
          user to give a specific name to describe this address. -->
     <string name="label_email_display_name">Display name</string>
 
-<string name="call_home">Call home</string>
-<string name="call_mobile">Call mobile</string>
-<string name="call_work">Call work</string>
-<string name="call_fax_work">Call work fax</string>
-<string name="call_fax_home">Call home fax</string>
-<string name="call_pager">Call pager</string>
-<string name="call_other">Call other</string>
-<string name="call_custom">Call <xliff:g id="custom">%s</xliff:g></string>
+    <!-- Action string for calling a custom phone number -->
+    <string name="call_custom">Call <xliff:g id="custom">%s</xliff:g></string>
+    <!-- Action string for calling a home phone number -->
+    <string name="call_home">Call home</string>
+    <!-- Action string for calling a mobile phone number -->
+    <string name="call_mobile">Call mobile</string>
+    <!-- Action string for calling a work phone number -->
+    <string name="call_work">Call work</string>
+    <!-- Action string for calling a work fax phone number -->
+    <string name="call_fax_work">Call work fax</string>
+    <!-- Action string for calling a home fax phone number -->
+    <string name="call_fax_home">Call home fax</string>
+    <!-- Action string for calling a pager phone number -->
+    <string name="call_pager">Call pager</string>
+    <!-- Action string for calling an other phone number -->
+    <string name="call_other">Call other</string>
+    <!-- Action string for calling a callback number -->
+    <string name="call_callback">Call callback</string>
+    <!-- Action string for calling a car phone number -->
+    <string name="call_car">Call car</string>
+    <!-- Action string for calling a company main phone number -->
+    <string name="call_company_main">Call company main</string>
+    <!-- Action string for calling a ISDN phone number -->
+    <string name="call_isdn">Call ISDN</string>
+    <!-- Action string for calling a main phone number -->
+    <string name="call_main">Call main</string>
+    <!-- Action string for calling an other fax phone number -->
+    <string name="call_other_fax">Call other fax</string>
+    <!-- Action string for calling a radio phone number -->
+    <string name="call_radio">Call radio</string>
+    <!-- Action string for calling a Telex phone number -->
+    <string name="call_telex">Call telex</string>
+    <!-- Action string for calling a TTY/TDD phone number -->
+    <string name="call_tty_tdd">Call TTY/TDD</string>
+    <!-- Action string for calling a work mobile phone number -->
+    <string name="call_work_mobile">Call work mobile</string>
+    <!-- Action string for calling a work pager phone number -->
+    <string name="call_work_pager">Call work pager</string>
+    <!-- Action string for calling an assistant phone number -->
+    <string name="call_assistant">Call <xliff:g id="assistant">%s</xliff:g></string>
+    <!-- Action string for calling a MMS phone number -->
+    <string name="call_mms">Call MMS</string>
 
-<!-- exchange specific -->
-<string name="call_home_2">Call home 2</string>
-<string name="call_work_2">Call work 2</string>
-<string name="call_car">Call car</string>
-<string name="call_company_main">Call company main</string>
-<string name="call_mms">Call MMS</string>
-<string name="call_radio">Call radio</string>
+    <!-- Action string for sending an SMS to a custom phone number -->
+    <string name="sms_custom">Text <xliff:g id="custom">%s</xliff:g></string>
+    <!-- Action string for sending an SMS to a home phone number -->
+    <string name="sms_home">Text home</string>
+    <!-- Action string for sending an SMS to a mobile phone number -->
+    <string name="sms_mobile">Text mobile</string>
+    <!-- Action string for sending an SMS to a work phone number -->
+    <string name="sms_work">Text work</string>
+    <!-- Action string for sending an SMS to a work fax phone number -->
+    <string name="sms_fax_work">Text work fax</string>
+    <!-- Action string for sending an SMS to a home fax phone number -->
+    <string name="sms_fax_home">Text home fax</string>
+    <!-- Action string for sending an SMS to a pager phone number -->
+    <string name="sms_pager">Text pager</string>
+    <!-- Action string for sending an SMS to an other phone number -->
+    <string name="sms_other">Text other</string>
+    <!-- Action string for sending an SMS to a callback number -->
+    <string name="sms_callback">Text callback</string>
+    <!-- Action string for sending an SMS to a car phone number -->
+    <string name="sms_car">Text car</string>
+    <!-- Action string for sending an SMS to a company main phone number -->
+    <string name="sms_company_main">Text company main</string>
+    <!-- Action string for sending an SMS to a ISDN phone number -->
+    <string name="sms_isdn">Text ISDN</string>
+    <!-- Action string for sending an SMS to a main phone number -->
+    <string name="sms_main">Text main</string>
+    <!-- Action string for sending an SMS to an other fax phone number -->
+    <string name="sms_other_fax">Text other fax</string>
+    <!-- Action string for sending an SMS to a radio phone number -->
+    <string name="sms_radio">Text radio</string>
+    <!-- Action string for sending an SMS to a Telex phone number -->
+    <string name="sms_telex">Text telex</string>
+    <!-- Action string for sending an SMS to a TTY/TDD phone number -->
+    <string name="sms_tty_tdd">Text TTY/TDD</string>
+    <!-- Action string for sending an SMS to a work mobile phone number -->
+    <string name="sms_work_mobile">Text work mobile</string>
+    <!-- Action string for sending an SMS to a work pager phone number -->
+    <string name="sms_work_pager">Text work pager</string>
+    <!-- Action string for sending an SMS to an assistant phone number -->
+    <string name="sms_assistant">Text <xliff:g id="assistant">%s</xliff:g></string>
+    <!-- Action string for sending an SMS to a MMS phone number -->
+    <string name="sms_mms">Text MMS</string>
 
+    <!-- Action string for sending an email to a home email address -->
+    <string name="email_home">Email home</string>
+    <!-- Action string for sending an email to a mobile email address -->
+    <string name="email_mobile">Email mobile</string>
+    <!-- Action string for sending an email to a work email address -->
+    <string name="email_work">Email work</string>
+    <!-- Action string for sending an email to an other email address -->
+    <string name="email_other">Email other</string>
+    <!-- Action string for sending an email to a custom email address -->
+    <string name="email_custom">Email <xliff:g id="custom">%s</xliff:g></string>
 
+    <!-- Generic action string for sending an email -->
+    <string name="email">Email</string>
 
+    <!-- Action string for viewing a home postal address -->
+    <string name="map_home">View home address</string>
+    <!-- Action string for viewing a work postal address -->
+    <string name="map_work">View work address</string>
+    <!-- Action string for viewing an other postal address -->
+    <string name="map_other">View other address</string>
+    <!-- Action string for viewing a custom postal address -->
+    <string name="map_custom">View <xliff:g id="custom">%s</xliff:g> address</string>
 
+    <!-- Action string for starting an IM chat with the AIM protocol -->
+    <string name="chat_aim">Chat using AIM</string>
+    <!-- Action string for starting an IM chat with the MSN or Windows Live protocol -->
+    <string name="chat_msn">Chat using Windows Live</string>
+    <!-- Action string for starting an IM chat with the Yahoo protocol -->
+    <string name="chat_yahoo">Chat using Yahoo</string>
+    <!-- Action string for starting an IM chat with the Skype protocol -->
+    <string name="chat_skype">Chat using Skype</string>
+    <!-- Action string for starting an IM chat with the QQ protocol -->
+    <string name="chat_qq">Chat using QQ</string>
+    <!-- Action string for starting an IM chat with the Google Talk protocol -->
+    <string name="chat_gtalk">Chat using Google Talk</string>
+    <!-- Action string for starting an IM chat with the ICQ protocol -->
+    <string name="chat_icq">Chat using ICQ</string>
+    <!-- Action string for starting an IM chat with the Jabber protocol -->
+    <string name="chat_jabber">Chat using Jabber</string>
 
-<string name="sms_home">Text home</string>
-<string name="sms_mobile">Text mobile</string>
-<string name="sms_work">Text work</string>
-<string name="sms_fax_work">Text work fax</string>
-<string name="sms_fax_home">Text home fax</string>
-<string name="sms_pager">Text pager</string>
-<string name="sms_other">Text other</string>
-<string name="sms_custom">Text <xliff:g id="custom">%s</xliff:g></string>
+    <!-- Generic action string for starting an IM chat -->
+    <string name="chat">Chat</string>
 
-<!-- exchange specific -->
-<string name="sms_home_2">Text home 2</string>
-<string name="sms_work_2">Text work 2</string>
-<string name="sms_car">Text car</string>
-<string name="sms_company_main">Text company main</string>
-<string name="sms_mms">Text MMS</string>
-<string name="sms_radio">Text radio</string>
+    <!-- Field title for the street of a structured postal address of a contact -->
+    <string name="postal_street">Street</string>
+    <!-- Field title for the PO box of a structured postal address of a contact -->
+    <string name="postal_pobox">PO box</string>
+    <!-- Field title for the neighborhood of a structured postal address of a contact -->
+    <string name="postal_neighborhood">Neighborhood</string>
+    <!-- Field title for the city of a structured postal address of a contact -->
+    <string name="postal_city">City</string>
+    <!-- Field title for the region, or state, of a structured postal address of a contact -->
+    <string name="postal_region">State</string>
+    <!-- Field title for the postal code of a structured postal address of a contact -->
+    <string name="postal_postcode">ZIP code</string>
+    <!-- Field title for the country of a structured postal address of a contact -->
+    <string name="postal_country">Country</string>
 
-
-
-<string name="email_home">Email home</string>
-<string name="email_mobile">Email mobile</string>
-<string name="email_work">Email work</string>
-<string name="email_other">Email other</string>
-<string name="email_custom">Email <xliff:g id="custom">%s</xliff:g></string>
-
-<string name="email">Email</string>
-
-
-
-<string name="map_home">View home address</string>
-<string name="map_work">View work address</string>
-<string name="map_other">View other address</string>
-<string name="map_custom">View <xliff:g id="custom">%s</xliff:g> address</string>
-
-<string name="chat_aim">Chat using AIM</string>
-<string name="chat_msn">Chat using Windows Live</string>
-<string name="chat_yahoo">Chat using Yahoo</string>
-<string name="chat_skype">Chat using Skype</string>
-<string name="chat_qq">Chat using QQ</string>
-<string name="chat_gtalk">Chat using Google Talk</string>
-<string name="chat_icq">Chat using ICQ</string>
-<string name="chat_jabber">Chat using Jabber</string>
-<string name="chat_other">Chat</string>
-
-<string name="postal_street">Street</string>
-<string name="postal_pobox">PO box</string>
-<string name="postal_neighborhood">Neighborhood</string>
-<string name="postal_city">City</string>
-<string name="postal_region">State</string>
-<string name="postal_postcode">ZIP code</string>
-<string name="postal_country">Country</string>
-
-
-<string name="name_given">Given name</string>
-<string name="name_family">Family name</string>
-<string name="name_prefix">Name prefix</string>
-<string name="name_middle">Middle name</string>
-<string name="name_suffix">Name suffix</string>
-<string name="name_phonetic_given">Phonetic given name</string>
-<string name="name_phonetic_middle">Phonetic middle name</string>
-<string name="name_phonetic_family">Phonetic family name</string>
-
-
+    <!-- Field title for the given name of a contact -->
+    <string name="name_given">Given name</string>
+    <!-- Field title for the family name of a contact -->
+    <string name="name_family">Family name</string>
+    <!-- Field title for the prefix name of a contact -->
+    <string name="name_prefix">Name prefix</string>
+    <!-- Field title for the middle name of a contact -->
+    <string name="name_middle">Middle name</string>
+    <!-- Field title for the suffix name of a contact -->
+    <string name="name_suffix">Name suffix</string>
+    <!-- Field title for the phonetic given name of a contact -->
+    <string name="name_phonetic_given">Phonetic given name</string>
+    <!-- Field title for the phonetic middle name of a contact -->
+    <string name="name_phonetic_middle">Phonetic middle name</string>
+    <!-- Field title for the phonetic family name of a contact -->
+    <string name="name_phonetic_family">Phonetic family name</string>
 
 </resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 125701b..5d47ee4 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -23,6 +23,7 @@
 import com.android.contacts.ui.DisplayGroupsActivity;
 import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
 import com.android.contacts.util.Constants;
+import com.google.android.collect.Lists;
 
 import android.accounts.Account;
 import android.app.Activity;
@@ -38,6 +39,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.UriMatcher;
 import android.content.res.Resources;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
@@ -50,6 +52,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.net.Uri.Builder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -348,6 +351,14 @@
     private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
     private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
 
+    // Uri matcher for contact id
+    private static final int CONTACTS_ID = 1001;
+    private static final UriMatcher CONTACTS_ID_MATCHER;
+    static {
+        CONTACTS_ID_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        CONTACTS_ID_MATCHER.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
+    }
+
     private class DeleteClickListener implements DialogInterface.OnClickListener {
         private Uri mUri;
 
@@ -484,12 +495,26 @@
         // dispatched from the SearchManager for security reasons
         // so we need to re-dispatch from here to the intended target.
         } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
+            Uri data = intent.getData();
+            Uri telUri = null;
+            if (CONTACTS_ID_MATCHER.match(data) == CONTACTS_ID) {
+                long contactId = Long.valueOf(data.getLastPathSegment());
+                final Cursor cursor = queryPhoneNumbers(contactId);
+                if (cursor != null) {
+                    if (cursor.getCount() == 1 && cursor.moveToFirst()) {
+                        int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
+                        String phoneNumber = cursor.getString(phoneNumberIndex);
+                        telUri = Uri.parse("tel:" + phoneNumber);
+                    }
+                    cursor.close();
+                }
+            }
             // See if the suggestion was clicked with a search action key (call button)
             Intent newIntent;
-            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
-                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
+                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
             } else {
-                newIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
+                newIntent = new Intent(Intent.ACTION_VIEW, data);
             }
             startActivity(newIntent);
             finish();
@@ -554,11 +579,7 @@
         // We manually save/restore the listview state
         list.setSaveEnabled(false);
 
-        if (mMode == MODE_JOIN_CONTACT) {
-            mQueryHandler = new SuggestionsQueryHandler(this, mQueryAggregateId);
-        } else {
-            mQueryHandler = new QueryHandler(this);
-        }
+        mQueryHandler = new QueryHandler(this);
         mJustCreated = true;
 
         // TODO(jham) redesign this
@@ -1543,6 +1564,17 @@
         }
     }
 
+    private Uri getJoinSuggestionsUri(String filter) {
+        Builder builder = Contacts.CONTENT_URI.buildUpon();
+        builder.appendEncodedPath(String.valueOf(mQueryAggregateId));
+        builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
+        if (!TextUtils.isEmpty(filter)) {
+            builder.appendEncodedPath(Uri.encode(filter));
+        }
+        builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
+        return builder.build();
+    }
+
     private static String getSortOrder(String[] projectionType) {
         /* if (Locale.getDefault().equals(Locale.JAPAN) &&
                 projectionType == AGGREGATES_PRIMARY_PHONE_PROJECTION) {
@@ -1559,6 +1591,7 @@
 
         // Cancel any pending queries
         mQueryHandler.cancelOperation(QUERY_TOKEN);
+        mQueryHandler.setLoadingJoinSuggestions(false);
 
         String[] projection = getProjectionForQuery();
         Uri uri = getUriToQuery();
@@ -1638,12 +1671,8 @@
                 break;
 
             case MODE_JOIN_CONTACT:
-                Uri suggestionsUri = Contacts.CONTENT_URI.buildUpon()
-                        .appendEncodedPath(String.valueOf(mQueryAggregateId))
-                        .appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY)
-                        .appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS))
-                        .build();
-                mQueryHandler.startQuery(QUERY_TOKEN, null, suggestionsUri, projection,
+                mQueryHandler.setLoadingJoinSuggestions(true);
+                mQueryHandler.startQuery(QUERY_TOKEN, null, getJoinSuggestionsUri(null), projection,
                         null, null, null);
                 break;
         }
@@ -1710,6 +1739,17 @@
 
             case MODE_LEGACY_PICK_PHONE: {
                 //TODO: Support filtering here (bug 2092503)
+                break;
+            }
+
+            case MODE_JOIN_CONTACT: {
+
+                // We are on a background thread. Run queries one after the other synchronously
+                Cursor cursor = resolver.query(getJoinSuggestionsUri(filter), projection, null,
+                        null, null);
+                mAdapter.setSuggestionsCursor(cursor);
+                return resolver.query(getContactFilterUri(filter), projection,
+                        getContactSelection(), null, getSortOrder(projection));
             }
         }
         throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
@@ -1821,16 +1861,39 @@
 
     private static class QueryHandler extends AsyncQueryHandler {
         protected final WeakReference<ContactsListActivity> mActivity;
+        protected boolean mLoadingJoinSuggestions = false;
 
         public QueryHandler(Context context) {
             super(context.getContentResolver());
             mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
         }
 
+        public void setLoadingJoinSuggestions(boolean flag) {
+            mLoadingJoinSuggestions = flag;
+        }
+
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
             final ContactsListActivity activity = mActivity.get();
             if (activity != null && !activity.isFinishing()) {
+
+                // Whenever we get a suggestions cursor, we need to immediately kick off
+                // another query for the complete list of contacts
+                if (cursor != null && mLoadingJoinSuggestions) {
+                    mLoadingJoinSuggestions = false;
+                    if (cursor.getCount() > 0) {
+                        activity.mAdapter.setSuggestionsCursor(cursor);
+                    } else {
+                        activity.mAdapter.setSuggestionsCursor(null);
+                    }
+
+                    startQuery(QUERY_TOKEN, null, activity.getContactFilterUri(activity.mQuery),
+                            CONTACTS_SUMMARY_PROJECTION,
+                            Contacts._ID + " != " + activity.mQueryAggregateId, null,
+                            getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+                    return;
+                }
+
                 activity.mAdapter.setLoading(false);
                 activity.getListView().clearTextFilter();
                 activity.mAdapter.changeCursor(cursor);
@@ -1850,46 +1913,6 @@
         }
     }
 
-    /**
-     * Query handler for the suggestions query used in the Join Contacts UI.  Once the
-     * suggestions query is complete, the handler launches an A-Z query.  The entire search is only
-     * done once the second query is complete.
-     */
-    private static final class SuggestionsQueryHandler extends QueryHandler {
-        boolean mSuggestionsQueryComplete;
-        private final long mAggregateId;
-
-        public SuggestionsQueryHandler(Context context, long aggregateId) {
-            super(context);
-            mAggregateId = aggregateId;
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (mSuggestionsQueryComplete) {
-                super.onQueryComplete(token, cookie, cursor);
-                return;
-            }
-
-            mSuggestionsQueryComplete = true;
-
-            final ContactsListActivity activity = mActivity.get();
-            if (activity != null && !activity.isFinishing()) {
-                if (cursor.getCount() > 0) {
-                    activity.mAdapter.setSuggestionsCursor(cursor);
-                } else {
-                    activity.mAdapter.setSuggestionsCursor(null);
-                }
-                startQuery(QUERY_TOKEN, null, Contacts.CONTENT_URI, CONTACTS_SUMMARY_PROJECTION,
-                        Contacts._ID + " != " + mAggregateId, null,
-                        getSortOrder(CONTACTS_SUMMARY_PROJECTION));
-
-            } else {
-                cursor.close();
-            }
-        }
-    }
-
     final static class ContactListItemCache {
         public TextView header;
         public View divider;
@@ -1923,7 +1946,6 @@
         private String mAlphabet;
         private boolean mLoading = true;
         private CharSequence mUnknownNameText;
-        private SparseArray<Integer> mLocalizedLabels;
         private boolean mDisplayPhotos = false;
         private boolean mDisplayCallButton = false;
         private boolean mDisplayAdditionalData = true;
@@ -1947,19 +1969,13 @@
             switch (mMode) {
                 case MODE_LEGACY_PICK_POSTAL:
                 case MODE_PICK_POSTAL:
-                    mLocalizedLabels = inflateLocalizedLabels(
-                            CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
                     mDisplaySectionHeaders = false;
                     break;
                 case MODE_LEGACY_PICK_PHONE:
                 case MODE_PICK_PHONE:
                     mDisplaySectionHeaders = false;
-                    mLocalizedLabels = inflateLocalizedLabels(
-                            CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
                     break;
                 default:
-                    mLocalizedLabels = inflateLocalizedLabels(
-                            CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
                     break;
             }
 
@@ -1991,29 +2007,6 @@
             }
         }
 
-        private SparseArray<Integer> inflateLocalizedLabels(String mimetype) {
-            SparseArray<Integer> localizedLabels = new SparseArray<Integer>();
-
-            Sources sources = Sources.getInstance(ContactsListActivity.this);
-
-            ContactsSource contactsSource = sources.getInflatedSource(null /*get fallback type*/,
-                    ContactsSource.LEVEL_MIMETYPES);
-            if (contactsSource == null) {
-                return localizedLabels;
-            }
-
-            DataKind kind = contactsSource.getKindForMimetype(mimetype);
-            if (kind == null) {
-                return localizedLabels;
-            }
-
-            for (EditType type : kind.typeList) {
-                localizedLabels.put(type.rawValue, type.labelRes);
-            }
-
-            return localizedLabels;
-        }
-
         private class ImageFetchHandler extends Handler {
 
             @Override
@@ -2317,7 +2310,7 @@
                         viewToUse.setImageResource(R.drawable.ic_contact_list_picture);
 
                         // Add it to a set of images that are populated asynchronously.
-                        mItemsMissingImages.add(cache.photoView);
+                        mItemsMissingImages.add(viewToUse);
 
                         if (mScrollState != OnScrollListener.SCROLL_STATE_FLING) {
 
@@ -2349,18 +2342,15 @@
             // Set the label.
             if (!cursor.isNull(typeColumnIndex)) {
                 labelView.setVisibility(View.VISIBLE);
-                int type = cursor.getInt(typeColumnIndex);
 
-                if (type != CommonDataKinds.BaseTypes.TYPE_CUSTOM) {
-                    try {
-                        labelView.setText(mLocalizedLabels.get(type));
-                    } catch (ArrayIndexOutOfBoundsException e) {
-                        labelView.setText(mLocalizedLabels.get(defaultType));
-                    }
+                final int type = cursor.getInt(typeColumnIndex);
+                final String label = cursor.getString(labelColumnIndex);
+
+                if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
+                    labelView.setText(StructuredPostal.getTypeLabel(context.getResources(), type,
+                            label));
                 } else {
-                    cursor.copyStringToBuffer(labelColumnIndex, cache.labelBuffer);
-                    // Don't check size, if it's zero just don't show anything
-                    labelView.setText(cache.labelBuffer.data, 0, cache.labelBuffer.sizeCopied);
+                    labelView.setText(Phone.getTypeLabel(context.getResources(), type, label));
                 }
             } else {
                 // There is no label, hide the the view
@@ -2405,6 +2395,7 @@
 
         @Override
         public void changeCursor(Cursor cursor) {
+
             // Get the split between starred and frequent items, if the mode is strequent
             mFrequentSeparatorPos = ListView.INVALID_POSITION;
             if (cursor != null && cursor.getCount() > 0 && mMode == MODE_STREQUENT) {
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index dea0bad..13a17c1 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -339,17 +339,7 @@
     public static View createTabIndicatorView(ViewGroup parent, ContactsSource source) {
         Drawable icon = null;
         if (source != null) {
-            final String packageName = source.resPackageName;
-            if (source.iconRes > 0) {
-                try {
-                    final Context authContext = parent.getContext().
-                            createPackageContext(packageName, 0);
-                    icon = authContext.getResources().getDrawable(source.iconRes);
-
-                } catch (PackageManager.NameNotFoundException e) {
-                    Log.d(TAG, "error getting the Package Context for " + packageName, e);
-                }
-            }
+            icon = source.getDisplayIcon(parent.getContext());
         }
         return createTabIndicatorView(parent, null, icon);
     }
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index a3d5de5..027c70e 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -264,7 +264,7 @@
                 mWakeLock.release();
                 mProgressDialogForReadVCard.dismiss();
                 // finish() is called via ErrorDisplayer() on failure.
-                if (shouldCallFinish) {
+                if (shouldCallFinish && !isFinishing()) {
                     if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
                         finish();
                     } else {
@@ -789,7 +789,23 @@
         super.onStop();
         if (mVCardReadThread != null) {
             // The Activity is no longer visible. Stop the thread.
-            // TODO: The Activity may be destroyed without this method being called.
+            mVCardReadThread.cancel();
+            mVCardReadThread = null;
+        }
+
+        // ImportVCardActivity should not be persistent. In other words, if there's some
+        // event calling onStop(), this Activity should finish its work and give the main
+        // screen back to the caller Activity.
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    public void finalize() {
+        if (mVCardReadThread != null) {
+            // Not sure this procedure is really needed, but just in case...
+            Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
             mVCardReadThread.cancel();
             mVCardReadThread = null;
         }
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
index 09cf5ec..ed78fd9 100644
--- a/src/com/android/contacts/SplitAggregateView.java
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -251,16 +251,7 @@
             ContactsSource source = mSources.getInflatedSource(info.accountType,
                     ContactsSource.LEVEL_SUMMARY);
             if (source != null) {
-                final String packageName = source.resPackageName;
-                if (source.iconRes > 0) {
-                    try {
-                        final Context context = getContext().createPackageContext(packageName, 0);
-                        icon = context.getResources().getDrawable(source.iconRes);
-
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Log.d(TAG, "error getting the Package Context for " + packageName, e);
-                    }
-                }
+                icon = source.getDisplayIcon(getContext());
             }
             if (icon != null) {
                 cache.sourceIcon.setImageDrawable(icon);
diff --git a/src/com/android/contacts/StyleManager.java b/src/com/android/contacts/StyleManager.java
deleted file mode 100644
index 2d24551..0000000
--- a/src/com/android/contacts/StyleManager.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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 com.android.contacts.model.Sources;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.WeakHashMap;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-
-/**
- * @deprecated Use {@link Sources} instead.
- */
-@Deprecated
-public final class StyleManager extends BroadcastReceiver {
-
-    public static final String TAG = "StyleManager";
-
-    private static StyleManager sInstance = null;
-
-    private WeakHashMap<String, Bitmap> mIconCache;
-    private HashMap<String, StyleSet> mStyleSetCache;
-
-    /*package*/ static final String DEFAULT_MIMETYPE = "default-icon";
-    private static final String ICON_SET_META_DATA = "com.android.contacts.iconset";
-    private static final String TAG_ICON_SET = "icon-set";
-    private static final String TAG_ICON = "icon";
-    private static final String TAG_ICON_DEFAULT = "icon-default";
-    private static final String KEY_JOIN_CHAR = "|";
-
-    private StyleManager(Context context) {
-        mIconCache = new WeakHashMap<String, Bitmap>();
-        mStyleSetCache = new HashMap<String, StyleSet>();
-        registerIntentReceivers(context);
-    }
-
-    /**
-     * Returns an instance of StyleManager. This method enforces that only a single instance of this
-     * class exists at any one time in a process.
-     *
-     * @param context A context object
-     * @return StyleManager object
-     */
-    public static StyleManager getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new StyleManager(context);
-        }
-        return sInstance;
-    }
-
-    private void registerIntentReceivers(Context context) {
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addDataScheme("package");
-
-        // We use getApplicationContext() so that the broadcast reciever can stay registered for
-        // the length of the application lifetime (instead of the calling activity's lifetime).
-        // This is so that we can notified of package changes, and purge the cache accordingly,
-        // but not be woken up if the application process isn't already running, since we will
-        // have no cache to clear at that point.
-        context.getApplicationContext().registerReceiver(this, filter);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        final String packageName = intent.getData().getSchemeSpecificPart();
-
-        if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
-                || Intent.ACTION_PACKAGE_ADDED.equals(action)
-                || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-            onPackageChange(packageName);
-        }
-    }
-
-    public void onPackageChange(String packageName) {
-        Iterator<String> itr;
-
-        // Remove cached icons for this package
-        for (itr = mIconCache.keySet().iterator(); itr.hasNext(); ) {
-            if (itr.next().startsWith(packageName + KEY_JOIN_CHAR)) {
-                itr.remove();
-            }
-        }
-
-        // Remove the cached style set for this package
-        mStyleSetCache.remove(packageName);
-    }
-
-    /**
-     * Get the default icon for a given package. If no icon is specified for that package
-     * null is returned.
-     *
-     * @param packageName
-     * @return Bitmap holding the default icon.
-     */
-    public Bitmap getDefaultIcon(Context context, String packageName) {
-        return getMimetypeIcon(context, packageName, DEFAULT_MIMETYPE);
-    }
-
-    /**
-     * Get the icon associated with a mimetype for a given package. If no icon is specified for that
-     * package null is returned.
-     *
-     * @param packageName
-     * @return Bitmap holding the default icon.
-     */
-    public Bitmap getMimetypeIcon(Context context, String packageName, String mimetype) {
-        String key = getKey(packageName, mimetype);
-
-        synchronized(mIconCache) {
-            if (!mIconCache.containsKey(key)) {
-                // Cache miss
-
-                // loadIcon() may return null, which is fine since, if no icon was found we want to
-                // store a null value so we know not to look next time.
-                mIconCache.put(key, loadIcon(context, packageName, mimetype));
-            }
-            return mIconCache.get(key);
-        }
-    }
-
-    private Bitmap loadIcon(Context context, String packageName, String mimetype) {
-        StyleSet ss = null;
-
-        synchronized(mStyleSetCache) {
-            if (!mStyleSetCache.containsKey(packageName)) {
-                // Cache miss
-                try {
-                    StyleSet inflated = inflateStyleSet(context, packageName);
-                    mStyleSetCache.put(packageName, inflated);
-                } catch (InflateException e) {
-                    // If inflation failed keep a null entry so we know not to try again.
-                    Log.w(TAG, "Inflation failed: " + e);
-                    mStyleSetCache.put(packageName, null);
-                }
-            }
-        }
-
-        ss = mStyleSetCache.get(packageName);
-        if (ss == null) {
-            return null;
-        }
-
-        int iconRes;
-        if ((iconRes = ss.getIconRes(mimetype)) == -1) {
-            return null;
-        }
-
-        return BitmapFactory.decodeResource(context.getResources(),
-                iconRes, null);
-    }
-
-    private StyleSet inflateStyleSet(Context context, String packageName) throws InflateException {
-        final PackageManager pm = context.getPackageManager();
-        final ApplicationInfo ai;
-
-        try {
-            ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
-        } catch (NameNotFoundException e) {
-            return null;
-        }
-
-        XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
-        final AttributeSet attrs = Xml.asAttributeSet(parser);
-
-        if (parser == null) {
-            return null;
-        }
-
-        try {
-            int type;
-            while ((type = parser.next()) != XmlPullParser.START_TAG
-                    && type != XmlPullParser.END_DOCUMENT) {
-                // Drain comments and whitespace
-            }
-
-            if (type != XmlPullParser.START_TAG) {
-                throw new InflateException("No start tag found");
-            }
-
-            if (!TAG_ICON_SET.equals(parser.getName())) {
-                throw new InflateException("Top level element must be StyleSet");
-            }
-
-            // Parse all children actions
-            StyleSet styleSet = new StyleSet();
-            final int depth = parser.getDepth();
-            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-                    && type != XmlPullParser.END_DOCUMENT) {
-                if (type == XmlPullParser.END_TAG) {
-                    continue;
-                }
-
-                TypedArray a;
-
-                String mimetype;
-                if (TAG_ICON.equals(parser.getName())) {
-                    a = context.obtainStyledAttributes(attrs, android.R.styleable.Icon);
-                    mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
-                    if (mimetype != null) {
-                        styleSet.addIcon(mimetype,
-                                a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
-                    }
-                } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
-                    a = context.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
-                    styleSet.addIcon(DEFAULT_MIMETYPE,
-                            a.getResourceId(
-                                    com.android.internal.R.styleable.IconDefault_icon, -1));
-                } else {
-                    throw new InflateException("Expected " + TAG_ICON + " or "
-                            + TAG_ICON_DEFAULT + " tag");
-                }
-            }
-            return styleSet;
-
-        } catch (XmlPullParserException e) {
-            throw new InflateException("Problem reading XML", e);
-        } catch (IOException e) {
-            throw new InflateException("Problem reading XML", e);
-        }
-    }
-
-    private String getKey(String packageName, String mimetype) {
-        return packageName + KEY_JOIN_CHAR + mimetype;
-    }
-
-    public static class InflateException extends Exception {
-        public InflateException(String message) {
-            super(message);
-        }
-
-        public InflateException(String message, Throwable throwable) {
-            super(message, throwable);
-        }
-    }
-
-    private static class StyleSet {
-        private HashMap<String, Integer> mMimetypeIconResMap;
-
-        public StyleSet() {
-            mMimetypeIconResMap = new HashMap<String, Integer>();
-        }
-
-        public int getIconRes(String mimetype) {
-            if (!mMimetypeIconResMap.containsKey(mimetype)) {
-                return -1;
-            }
-            return mMimetypeIconResMap.get(mimetype);
-        }
-
-        public void addIcon(String mimetype, int res) {
-            if (mimetype == null) {
-                return;
-            }
-            mMimetypeIconResMap.put(mimetype, res);
-        }
-    }
-
-    //-------------------------------------------//
-    //-- Methods strictly for testing purposes --//
-    //-------------------------------------------//
-
-    /*package*/ int getIconCacheSize() {
-        return mIconCache.size();
-    }
-
-    /*package*/ int getStyleSetCacheSize() {
-        return mStyleSetCache.size();
-    }
-
-    /*package*/ boolean isStyleSetCacheHit(String packageName) {
-        return mStyleSetCache.containsKey(packageName);
-    }
-
-    /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
-        return mIconCache.containsKey(getKey(packageName, mimetype));
-    }
-
-    //-------------------------------------------//
-}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index be78bea..76c7b7e 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -302,7 +302,8 @@
             // TODO: ensure inflation on background task so we don't block UI thread here
             final ContactsSource source = sources.getInflatedSource(accountType,
                     ContactsSource.LEVEL_SUMMARY);
-            addTab(rawContactId, ContactsUtils.createTabIndicatorView(mTabWidget.getTabParent(), source));
+            addTab(rawContactId, ContactsUtils.createTabIndicatorView(mTabWidget.getTabParent(),
+                    source));
         }
     }
 
@@ -846,12 +847,11 @@
             for (Entity entity: mEntities) {
                 final ContentValues entValues = entity.getEntityValues();
                 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
-                // TODO: entry.contactId should be renamed to entry.rawContactId
-                long contactId = entValues.getAsLong(RawContacts._ID);
+                final long rawContactId = entValues.getAsLong(RawContacts._ID);
 
                 // This performs the tab filtering
                 if (mSelectedRawContactId != null
-                        && mSelectedRawContactId != contactId
+                        && mSelectedRawContactId != rawContactId
                         && mSelectedRawContactId != ALL_CONTACTS_ID) {
                     continue;
                 }
@@ -859,26 +859,19 @@
                 for (NamedContentValues subValue : entity.getSubValues()) {
                     ViewEntry entry = new ViewEntry();
 
-                    ContentValues entryValues = subValue.values;
+                    final ContentValues entryValues = subValue.values;
+                    entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
+
                     final String mimetype = entryValues.getAsString(Data.MIMETYPE);
-                    if (mimetype == null || accountType == null) {
-                        continue;
-                    }
+                    if (mimetype == null) continue;
 
-                    ContactsSource contactsSource = sources.getInflatedSource(accountType,
+                    final DataKind kind = sources.getKindOrFallback(accountType, mimetype, this,
                             ContactsSource.LEVEL_MIMETYPES);
-                    if (contactsSource == null) {
-                        continue;
-                    }
-
-                    DataKind kind = contactsSource.getKindForMimetype(mimetype);
-                    if (kind == null) {
-                        continue;
-                    }
+                    if (kind == null) continue;
 
                     final long id = entryValues.getAsLong(Data._ID);
                     final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
-                    entry.contactId = contactId;
+                    entry.contactId = rawContactId;
                     entry.id = id;
                     entry.uri = uri;
                     entry.mimetype = mimetype;
@@ -888,12 +881,12 @@
                         entry.type = entryValues.getAsInteger(kind.typeColumn);
                     }
                     if (kind.iconRes > 0) {
+                        entry.resPackageName = kind.resPackageName;
                         entry.actionIcon = kind.iconRes;
                     }
 
                     // Don't crash if the data is bogus
                     if (TextUtils.isEmpty(entry.data)) {
-                        Log.w(TAG, "empty data for contact method " + id);
                         continue;
                     }
 
@@ -938,10 +931,6 @@
                             // Build email entries
                             entry.intent = new Intent(Intent.ACTION_SENDTO,
                                     Uri.fromParts("mailto", entry.data, null));
-                            // Temporary hack until we get real label resources for exchange.
-                            if (TextUtils.isEmpty(entry.label)) {
-                                entry.label = getString(R.string.email).toLowerCase();
-                            }
                             entry.isPrimary = isSuperPrimary;
                             mEmailEntries.add(entry);
                         } else if (CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE.
@@ -956,7 +945,7 @@
                             String host = null;
 
                             if (TextUtils.isEmpty(entry.label)) {
-                                entry.label = getString(R.string.chat_other).toLowerCase();
+                                entry.label = getString(R.string.chat).toLowerCase();
                             }
 
                             if (protocolObj instanceof Number) {
@@ -995,9 +984,12 @@
                         entry.intent = null;
                         entry.maxLines = 10;
                         mOtherEntries.add(entry);
+                    } else {
+                        // Handle showing custom
+                        entry.intent = new Intent(Intent.ACTION_VIEW, uri);
+                        mOtherEntries.add(entry);
                     }
 
-
                     // TODO(emillar) Add group entries
                     //              // Build the group entries
                     //              final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
@@ -1069,6 +1061,7 @@
      * A basic structure with the data for a contact entry in the list.
      */
     static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
+        public String resPackageName = null;
         public int actionIcon = -1;
         public boolean isPrimary = false;
         public int presenceIcon = -1;
@@ -1225,7 +1218,15 @@
             // Set the action icon
             ImageView action = views.actionIcon;
             if (entry.actionIcon != -1) {
-                action.setImageDrawable(resources.getDrawable(entry.actionIcon));
+                Drawable actionIcon;
+                if (entry.resPackageName != null) {
+                    // Load external resources through PackageManager
+                    actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
+                            entry.actionIcon, null);
+                } else {
+                    actionIcon = resources.getDrawable(entry.actionIcon);
+                }
+                action.setImageDrawable(actionIcon);
                 action.setVisibility(View.VISIBLE);
             } else {
                 // Things should still line up as if there was an icon, so make it invisible
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index 1e797d4..f0c21e3 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -17,17 +17,14 @@
 package com.android.contacts.model;
 
 import com.google.android.collect.Lists;
-
-import org.xmlpull.v1.XmlPullParser;
+import com.google.android.collect.Maps;
 
 import android.accounts.Account;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.XmlResourceParser;
 import android.database.Cursor;
+import android.graphics.drawable.Drawable;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
@@ -38,41 +35,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 
-/*
-
-<!-- example of what SourceConstraints would look like in XML -->
-<!-- NOTE: may not directly match the current structure version -->
-
-<DataKind
-    mimeType="vnd.android.cursor.item/email"
-    title="@string/title_postal"
-    icon="@drawable/icon_postal"
-    weight="12"
-    editable="true">
-
-    <!-- these are defined using string-builder-ish -->
-    <ActionHeader></ActionHeader>
-    <ActionBody socialSummary="true" />  <!-- can pull together various columns -->
-
-    <!-- ordering handles precedence the "insert/add" case -->
-    <!-- assume uniform type when missing "column", use title in place -->
-    <EditTypes column="data5" overallMax="-1">
-        <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
-        <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
-        <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
-    </EditTypes>
-
-    <!-- when single edit field, simplifies edit case -->
-    <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
-    <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
-    <EditField column="data3" title="@string/field_suffix" />
-
-</DataKind>
-
-*/
-
 /**
  * Internal structure that represents constraints and styles for a specific data
  * source, such as the various data types they support, including details on how
@@ -80,7 +45,7 @@
  * <p>
  * In the future this may be inflated from XML defined by a data source.
  */
-public class ContactsSource {
+public abstract class ContactsSource {
     /**
      * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
      */
@@ -91,6 +56,7 @@
      * {@link Account} or for matching against {@link Data#RES_PACKAGE}.
      */
     public String resPackageName;
+    public String summaryResPackageName;
 
     public int titleRes;
     public int iconRes;
@@ -102,14 +68,17 @@
      */
     private ArrayList<DataKind> mKinds = Lists.newArrayList();
 
-    private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
-    private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+    /**
+     * Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
+     */
+    private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
 
+    public static final int LEVEL_NONE = 0;
     public static final int LEVEL_SUMMARY = 1;
     public static final int LEVEL_MIMETYPES = 2;
     public static final int LEVEL_CONSTRAINTS = 3;
 
-    private int mInflatedLevel = -1;
+    private int mInflatedLevel = LEVEL_NONE;
 
     public synchronized boolean isInflated(int inflateLevel) {
         return mInflatedLevel >= inflateLevel;
@@ -121,62 +90,54 @@
     }
 
     /**
-     * Ensure that the constraint rules behind this {@link ContactsSource} have
-     * been inflated. Because this may involve parsing meta-data from
-     * {@link PackageManager}, it shouldn't be called from a UI thread.
+     * Ensure that this {@link ContactsSource} has been inflated to the
+     * requested level.
      */
     public synchronized void ensureInflated(Context context, int inflateLevel) {
-        if (isInflated(inflateLevel)) return;
-        // TODO: handle inflating at multiple levels of parsing
-        mInflatedLevel = inflateLevel;
-        mKinds.clear();
-
-        // Handle some well-known sources with hard-coded constraints
-        // TODO: move these into adapter-specific XML once schema finalized
-        if (HardCodedSources.ACCOUNT_TYPE_FALLBACK.equals(accountType)) {
-            HardCodedSources.buildFallback(context, this);
-            return;
-        } else if (HardCodedSources.ACCOUNT_TYPE_GOOGLE.equals(accountType)) {
-            HardCodedSources.buildGoogle(context, this);
-            return;
-        } else if(HardCodedSources.ACCOUNT_TYPE_EXCHANGE.equals(accountType)) {
-            HardCodedSources.buildExchange(context, this);
-            return;
-        } else if(HardCodedSources.ACCOUNT_TYPE_FACEBOOK.equals(accountType)) {
-            HardCodedSources.buildFacebook(context, this);
-            return;
-        }
-
-        // Handle unknown sources by searching their package
-        final PackageManager pm = context.getPackageManager();
-        final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
-        final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
-                PackageManager.GET_META_DATA);
-        for (ResolveInfo info : matches) {
-            final XmlResourceParser parser = info.activityInfo.loadXmlMetaData(pm,
-                    METADATA_CONTACTS);
-            inflate(parser);
+        if (!isInflated(inflateLevel)) {
+            inflate(context, inflateLevel);
         }
     }
 
     /**
-     * Inflate this {@link ContactsSource} from the given parser. This may only
-     * load details matching the publicly-defined schema.
+     * Perform the actual inflation to the requested level. Called by
+     * {@link #ensureInflated(Context, int)} when inflation is needed.
      */
-    protected void inflate(XmlPullParser parser) {
-        // TODO: implement basic functionality for third-party integration
-        throw new UnsupportedOperationException("Custom constraint parser not implemented");
+    protected abstract void inflate(Context context, int inflateLevel);
+
+    /**
+     * Invalidate any cache for this {@link ContactsSource}, removing all
+     * inflated data. Calling {@link #ensureInflated(Context, int)} will
+     * populate again from scratch.
+     */
+    public synchronized void invalidateCache() {
+        this.mKinds.clear();
+        this.mMimeKinds.clear();
+        setInflatedLevel(LEVEL_NONE);
     }
 
     public CharSequence getDisplayLabel(Context context) {
-        if (this.titleRes > 0) {
+        if (this.titleRes != -1 && this.summaryResPackageName != null) {
             final PackageManager pm = context.getPackageManager();
-            return pm.getText(this.resPackageName, this.titleRes, null);
+            return pm.getText(this.summaryResPackageName, this.titleRes, null);
+        } else if (this.titleRes != -1) {
+            return context.getText(this.titleRes);
         } else {
             return this.accountType;
         }
     }
 
+    public Drawable getDisplayIcon(Context context) {
+        if (this.titleRes != -1 && this.summaryResPackageName != null) {
+            final PackageManager pm = context.getPackageManager();
+            return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
+        } else if (this.titleRes != -1) {
+            return context.getResources().getDrawable(this.iconRes);
+        } else {
+            return null;
+        }
+    }
+
     /**
      * {@link Comparator} to sort by {@link DataKind#weight}.
      */
@@ -197,20 +158,22 @@
     }
 
     /**
-     * Find the {@link DataKind} for a specifc MIME-type, if it's handled by
-     * this data source.
+     * Find the {@link DataKind} for a specific MIME-type, if it's handled by
+     * this data source. If you may need a fallback {@link DataKind}, use
+     * {@link Sources#getKindOrFallback(String, String, Context, int)}.
      */
     public DataKind getKindForMimetype(String mimeType) {
-        for (DataKind kind : mKinds) {
-            if (mimeType.equals(kind.mimeType)) {
-                return kind;
-            }
-        }
-        return null;
+        return this.mMimeKinds.get(mimeType);
     }
 
-    public void add(DataKind kind) {
+    /**
+     * Add given {@link DataKind} to list of those provided by this source.
+     */
+    public DataKind addKind(DataKind kind) {
+        kind.resPackageName = this.resPackageName;
         this.mKinds.add(kind);
+        this.mMimeKinds.put(kind.mimeType, kind);
+        return kind;
     }
 
     /**
@@ -220,6 +183,7 @@
      * labels and editable {@link EditField}.
      */
     public static class DataKind {
+        public String resPackageName;
         public String mimeType;
         public int titleRes;
         public int iconRes;
@@ -231,6 +195,7 @@
         public StringInflater actionHeader;
         public StringInflater actionAltHeader;
         public StringInflater actionBody;
+        public StringInflater actionFooter;
         public boolean actionBodySocial;
         public boolean actionBodyCombine;
 
@@ -242,6 +207,9 @@
 
         public ContentValues defaultValues;
 
+        public DataKind() {
+        }
+
         public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
             this.mimeType = mimeType;
             this.titleRes = titleRes;
@@ -261,8 +229,8 @@
     public static class EditType {
         public int rawValue;
         public int labelRes;
-        public int actionRes;
-        public int actionAltRes;
+//        public int actionRes;
+//        public int actionAltRes;
         public boolean secondary;
         public int specificMax;
         public String customColumn;
@@ -273,16 +241,6 @@
             this.specificMax = -1;
         }
 
-        public EditType(int rawValue, int labelRes, int actionRes) {
-            this(rawValue, labelRes);
-            this.actionRes = actionRes;
-        }
-
-        public EditType(int rawValue, int labelRes, int actionRes, int actionAltRes) {
-            this(rawValue, labelRes, actionRes);
-            this.actionAltRes = actionAltRes;
-        }
-
         public EditType setSecondary(boolean secondary) {
             this.secondary = secondary;
             return this;
@@ -335,9 +293,9 @@
             this.inputType = inputType;
         }
 
-        public EditField(String column, int titleRes, int inputType, boolean optional) {
-            this(column, titleRes, inputType);
+        public EditField setOptional(boolean optional) {
             this.optional = optional;
+            return this;
         }
     }
 
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
new file mode 100644
index 0000000..c017037
--- /dev/null
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -0,0 +1,273 @@
+/*
+ * 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.model;
+
+import com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+public class ExchangeSource extends FallbackSource {
+
+    public static final String ACCOUNT_TYPE = "com.android.exchange";
+
+    public ExchangeSource(String resPackageName) {
+        this.accountType = ACCOUNT_TYPE;
+        this.resPackageName = null;
+        this.summaryResPackageName = resPackageName;
+    }
+
+    @Override
+    protected void inflate(Context context, int inflateLevel) {
+
+        inflateStructuredName(inflateLevel);
+        inflateNickname(inflateLevel);
+        inflatePhone(inflateLevel);
+        inflateEmail(inflateLevel);
+        inflateStructuredPostal(inflateLevel);
+        inflateIm(inflateLevel);
+        inflateOrganization(inflateLevel);
+        inflatePhoto(inflateLevel);
+        inflateNote(inflateLevel);
+        inflateWebsite(inflateLevel);
+
+        setInflatedLevel(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateStructuredName(int inflateLevel) {
+        final DataKind kind = super.inflateStructuredName(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 1;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+                    FLAGS_PERSON_NAME));
+            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+                    FLAGS_PERSON_NAME));
+            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                    R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                    R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateNickname(int inflateLevel) {
+        final DataKind kind = super.inflateNickname(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 1;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+                    FLAGS_PERSON_NAME));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflatePhone(int inflateLevel) {
+        final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Phone.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildPhoneType(Phone.TYPE_HOME).setSpecificMax(2));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE).setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK).setSpecificMax(2));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true)
+                    .setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true)
+                    .setSpecificMax(1));
+            kind.typeList
+                    .add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true).setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true).setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true)
+                    .setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true).setSpecificMax(1));
+            kind.typeList
+                    .add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true)
+                    .setSpecificMax(1).setCustomColumn(Phone.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateEmail(int inflateLevel) {
+        final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 3;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateStructuredPostal(int inflateLevel) {
+        final DataKind kind = super.inflateStructuredPostal(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = StructuredPostal.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK).setSpecificMax(1));
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME).setSpecificMax(1));
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER).setSpecificMax(1));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
+                    FLAGS_POSTAL).setOptional(true));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateIm(int inflateLevel) {
+        final DataKind kind = super.inflateIm(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 3;
+
+            // NOTE: even though a traditional "type" exists, for editing
+            // purposes we're using the protocol to pick labels
+
+            kind.defaultValues = new ContentValues();
+            kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+            kind.typeColumn = Im.PROTOCOL;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+            kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+            kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+            kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+            kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+            kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+            kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+            kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+            kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+                    Im.CUSTOM_PROTOCOL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateOrganization(int inflateLevel) {
+        final DataKind kind = super.inflateOrganization(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Organization.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildOrgType(Organization.TYPE_WORK).setSpecificMax(1));
+            kind.typeList.add(buildOrgType(Organization.TYPE_OTHER).setSpecificMax(1));
+            kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+                    .setSpecificMax(1));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+                    FLAGS_GENERIC_NAME));
+            kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+                    FLAGS_GENERIC_NAME));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflatePhoto(int inflateLevel) {
+        final DataKind kind = super.inflatePhoto(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 1;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateNote(int inflateLevel) {
+        final DataKind kind = super.inflateNote(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 1;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateWebsite(int inflateLevel) {
+        final DataKind kind = super.inflateWebsite(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeOverallMax = 1;
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+        }
+
+        return kind;
+    }
+}
diff --git a/src/com/android/contacts/model/ExternalSource.java b/src/com/android/contacts/model/ExternalSource.java
new file mode 100644
index 0000000..f22aba4
--- /dev/null
+++ b/src/com/android/contacts/model/ExternalSource.java
@@ -0,0 +1,261 @@
+/*
+ * 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.model;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.provider.ContactsContract.Data;
+import android.provider.SocialContract.Activities;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.util.List;
+
+/*
+
+<!-- example of what SourceConstraints would look like in XML -->
+<!-- NOTE: may not directly match the current structure version -->
+
+<DataKind
+    mimeType="vnd.android.cursor.item/email"
+    title="@string/title_postal"
+    icon="@drawable/icon_postal"
+    weight="12"
+    editable="true">
+
+    <!-- these are defined using string-builder-ish -->
+    <ActionHeader></ActionHeader>
+    <ActionBody socialSummary="true" />  <!-- can pull together various columns -->
+
+    <!-- ordering handles precedence the "insert/add" case -->
+    <!-- assume uniform type when missing "column", use title in place -->
+    <EditTypes column="data5" overallMax="-1">
+        <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
+        <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
+        <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
+    </EditTypes>
+
+    <!-- when single edit field, simplifies edit case -->
+    <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
+    <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
+    <EditField column="data3" title="@string/field_suffix" />
+
+</DataKind>
+
+*/
+
+/**
+ * Internal structure that represents constraints and styles for a specific data
+ * source, such as the various data types they support, including details on how
+ * those types should be rendered and edited.
+ * <p>
+ * In the future this may be inflated from XML defined by a data source.
+ */
+public class ExternalSource extends ContactsSource {
+    private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
+    private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+
+    private interface InflateTags {
+        final String CONTACTS_SOURCE = "ContactsSource";
+        final String CONTACTS_DATA_KIND = "ContactsDataKind";
+    }
+
+    public ExternalSource(String resPackageName) {
+        this.resPackageName = resPackageName;
+        this.summaryResPackageName = resPackageName;
+    }
+
+    /**
+     * Ensure that the constraint rules behind this {@link ContactsSource} have
+     * been inflated. Because this may involve parsing meta-data from
+     * {@link PackageManager}, it shouldn't be called from a UI thread.
+     */
+    @Override
+    public void inflate(Context context, int inflateLevel) {
+        // Handle unknown sources by searching their package
+        final PackageManager pm = context.getPackageManager();
+        final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
+        final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
+                PackageManager.GET_META_DATA);
+        for (ResolveInfo info : matches) {
+            final XmlResourceParser parser = info.serviceInfo.loadXmlMetaData(pm,
+                    METADATA_CONTACTS);
+            if (parser == null) continue;
+            inflate(context, parser);
+        }
+    }
+
+    /**
+     * Inflate this {@link ContactsSource} from the given parser. This may only
+     * load details matching the publicly-defined schema.
+     */
+    protected void inflate(Context context, XmlPullParser parser) {
+        final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+        try {
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Drain comments and whitespace
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("No start tag found");
+            }
+
+            if (!InflateTags.CONTACTS_SOURCE.equals(parser.getName())) {
+                throw new IllegalStateException("Top level element must be "
+                        + InflateTags.CONTACTS_SOURCE);
+            }
+
+            // Parse all children kinds
+            final int depth = parser.getDepth();
+            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && type != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.END_TAG
+                        || !InflateTags.CONTACTS_DATA_KIND.equals(parser.getName())) {
+                    continue;
+                }
+
+                final TypedArray a = context.obtainStyledAttributes(attrs,
+                        android.R.styleable.ContactsDataKind);
+                final DataKind kind = new DataKind();
+
+                kind.mimeType = a
+                        .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
+                kind.iconRes = a.getResourceId(
+                        com.android.internal.R.styleable.ContactsDataKind_icon, -1);
+
+                final String summaryColumn = a
+                        .getString(com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
+                if (summaryColumn != null) {
+                    // Inflate a specific column as summary when requested
+                    kind.actionHeader = new FallbackSource.SimpleInflater(summaryColumn);
+                }
+
+                final String detailColumn = a
+                        .getString(com.android.internal.R.styleable.ContactsDataKind_detailColumn);
+                final boolean detailSocialSummary = a.getBoolean(
+                        com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
+                        false);
+                if (detailSocialSummary) {
+                    // Inflate social summary when requested
+                    kind.actionBody = new SocialInflater(false);
+                    kind.actionFooter = new SocialInflater(true);
+                } else {
+                    // Otherwise inflate specific column as summary
+                    kind.actionBody = new FallbackSource.SimpleInflater(detailColumn);
+                }
+
+                addKind(kind);
+            }
+        } catch (XmlPullParserException e) {
+            throw new IllegalStateException("Problem reading XML", e);
+        } catch (IOException e) {
+            throw new IllegalStateException("Problem reading XML", e);
+        }
+    }
+
+    /**
+     * Temporary cache to hold recent social data.
+     */
+    private static class SocialCache {
+        private static Status sLastStatus = null;
+
+        public static class Status {
+            public long rawContactId;
+            public CharSequence title;
+            public long published;
+        }
+
+        public static synchronized Status getLatestStatus(Context context, long rawContactId) {
+            if (sLastStatus == null || sLastStatus.rawContactId != rawContactId) {
+                // Cache missing, or miss, so query directly
+                sLastStatus = queryLatestStatus(context, rawContactId);
+            }
+            return sLastStatus;
+        }
+
+        private static Status queryLatestStatus(Context context, long rawContactId) {
+            // Find latest social update by this person, filtering to show only
+            // original content and avoid replies.
+            final ContentResolver resolver = context.getContentResolver();
+            final Cursor cursor = resolver.query(Activities.CONTENT_URI, new String[] {
+                Activities.TITLE, Activities.PUBLISHED
+            }, Activities.AUTHOR_CONTACT_ID + "=" + rawContactId + " AND "
+                    + Activities.IN_REPLY_TO + " IS NULL", null, Activities.PUBLISHED + " DESC");
+
+            final Status status = new Status();
+            try {
+                if (cursor != null && cursor.moveToFirst()) {
+                    status.title = cursor.getString(0);
+                    status.published = cursor.getLong(1);
+                }
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+            return status;
+        }
+    }
+
+    /**
+     * Inflater that will return the latest {@link Activities#TITLE} and
+     * {@link Activities#PUBLISHED} for the given {@link Data#RAW_CONTACT_ID}.
+     */
+    protected static class SocialInflater implements StringInflater {
+        private final boolean mPublishedMode;
+
+        public SocialInflater(boolean publishedMode) {
+            mPublishedMode = publishedMode;
+        }
+
+        protected CharSequence inflatePublished(long published) {
+            return DateUtils.getRelativeTimeSpanString(published, System.currentTimeMillis(),
+                    DateUtils.MINUTE_IN_MILLIS);
+        }
+
+        /** {@inheritDoc} */
+        public CharSequence inflateUsing(Context context, Cursor cursor) {
+            final Long rawContactId = cursor.getLong(cursor.getColumnIndex(Data.RAW_CONTACT_ID));
+            if (rawContactId == null) return null;
+
+            final SocialCache.Status status = SocialCache.getLatestStatus(context, rawContactId);
+            return mPublishedMode ? inflatePublished(status.published) : status.title;
+        }
+
+        /** {@inheritDoc} */
+        public CharSequence inflateUsing(Context context, ContentValues values) {
+            final Long rawContactId = values.getAsLong(Data.RAW_CONTACT_ID);
+            if (rawContactId == null) return null;
+
+            final SocialCache.Status status = SocialCache.getLatestStatus(context, rawContactId);
+            return mPublishedMode ? inflatePublished(status.published) : status.title;
+        }
+    }
+}
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
new file mode 100644
index 0000000..85aedd7
--- /dev/null
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -0,0 +1,579 @@
+/*
+ * 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.model;
+
+import com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+public class FallbackSource extends ContactsSource {
+    protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
+    protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+    protected static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
+    protected static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
+    protected static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+    protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+    protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_VARIATION_URI;
+    protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
+            | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
+            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+
+    public FallbackSource() {
+        this.accountType = null;
+        this.titleRes = R.string.account_phone;
+        this.iconRes = R.drawable.ic_launcher_contacts;
+    }
+
+    @Override
+    protected void inflate(Context context, int inflateLevel) {
+
+        inflateStructuredName(inflateLevel);
+        inflateNickname(inflateLevel);
+        inflatePhone(inflateLevel);
+        inflateEmail(inflateLevel);
+        inflateStructuredPostal(inflateLevel);
+        inflateIm(inflateLevel);
+        inflateOrganization(inflateLevel);
+        inflatePhoto(inflateLevel);
+        inflateNote(inflateLevel);
+        inflateWebsite(inflateLevel);
+
+        setInflatedLevel(inflateLevel);
+
+    }
+
+    protected EditType buildPhoneType(int type) {
+        return new EditType(type, Phone.getTypeLabelResource(type));
+    }
+
+    protected EditType buildEmailType(int type) {
+        return new EditType(type, Email.getTypeLabelResource(type));
+    }
+
+    protected EditType buildPostalType(int type) {
+        return new EditType(type, StructuredPostal.getTypeLabelResource(type));
+    }
+
+    protected EditType buildImType(int type) {
+        return new EditType(type, Im.getProtocolLabelResource(type));
+    }
+
+    protected EditType buildOrgType(int type) {
+        return new EditType(type, Organization.getTypeLabelResource(type));
+    }
+
+    protected DataKind inflateStructuredName(int inflateLevel) {
+        DataKind kind = getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
+                    R.string.nameLabelsGroup, -1, -1, true));
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+                    FLAGS_PERSON_NAME));
+            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+                    FLAGS_PERSON_NAME));
+            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+                    FLAGS_PERSON_NAME).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                    R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+                    R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                    R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateNickname(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
+                    R.string.nicknameLabelsGroup, -1, 115, true));
+            kind.secondary = true;
+            kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
+            kind.actionBody = new SimpleInflater(Nickname.NAME);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+                    FLAGS_PERSON_NAME));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflatePhone(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
+                    android.R.drawable.sym_action_call, 10, true));
+            kind.iconAltRes = R.drawable.sym_action_sms;
+            kind.actionHeader = new PhoneActionInflater();
+            kind.actionAltHeader = new PhoneActionAltInflater();
+            kind.actionBody = new SimpleInflater(Phone.NUMBER);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Phone.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+                    Phone.LABEL));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
+                    .setCustomColumn(Phone.LABEL));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateEmail(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Email.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE,
+                    R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true));
+            kind.actionHeader = new EmailActionInflater();
+            kind.actionBody = new SimpleInflater(Email.DATA);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Email.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+            kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+            kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+            kind.typeList.add(buildEmailType(Email.TYPE_MOBILE));
+            kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+                    Email.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateStructuredPostal(int inflateLevel) {
+        DataKind kind = getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
+                    R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true));
+            kind.actionHeader = new PostalActionInflater();
+            kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = StructuredPostal.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK));
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER));
+            kind.typeList.add(buildPostalType(StructuredPostal.TYPE_CUSTOM).setSecondary(true)
+                    .setCustomColumn(StructuredPostal.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
+                    FLAGS_POSTAL).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
+                    R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
+            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
+                    FLAGS_POSTAL));
+            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
+                    FLAGS_POSTAL).setOptional(true));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateIm(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
+                    android.R.drawable.sym_action_chat, 20, true));
+            kind.actionHeader = new ImActionInflater();
+            kind.actionBody = new SimpleInflater(Im.DATA);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            // NOTE: even though a traditional "type" exists, for editing
+            // purposes we're using the protocol to pick labels
+
+            kind.defaultValues = new ContentValues();
+            kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+            kind.typeColumn = Im.PROTOCOL;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+            kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+            kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+            kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+            kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+            kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+            kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+            kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+            kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+                    Im.CUSTOM_PROTOCOL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateOrganization(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
+                    R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true));
+            kind.actionHeader = new SimpleInflater(Organization.COMPANY);
+            kind.actionBody = new SimpleInflater(Organization.TITLE);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Organization.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildOrgType(Organization.TYPE_WORK));
+            kind.typeList.add(buildOrgType(Organization.TYPE_OTHER));
+            kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+                    .setCustomColumn(Organization.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+                    FLAGS_GENERIC_NAME));
+            kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+                    FLAGS_GENERIC_NAME));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflatePhoto(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true));
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateNote(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Note.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
+                    R.string.label_notes, R.drawable.sym_note, 110, true));
+            kind.secondary = true;
+            kind.actionHeader = new SimpleInflater(R.string.label_notes);
+            kind.actionBody = new SimpleInflater(Note.NOTE);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+        }
+
+        return kind;
+    }
+
+    protected DataKind inflateWebsite(int inflateLevel) {
+        DataKind kind = getKindForMimetype(Website.CONTENT_ITEM_TYPE);
+        if (kind == null) {
+            kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
+                    R.string.websiteLabelsGroup, -1, 120, true));
+            kind.secondary = true;
+            kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
+            kind.actionBody = new SimpleInflater(Website.URL);
+        }
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+        }
+
+        return kind;
+    }
+
+    /**
+     * Simple inflater that assumes a string resource has a "%s" that will be
+     * filled from the given column.
+     */
+    public static class SimpleInflater implements StringInflater {
+        private final int mStringRes;
+        private final String mColumnName;
+
+        public SimpleInflater(int stringRes) {
+            this(stringRes, null);
+        }
+
+        public SimpleInflater(String columnName) {
+            this(-1, columnName);
+        }
+
+        public SimpleInflater(int stringRes, String columnName) {
+            mStringRes = stringRes;
+            mColumnName = columnName;
+        }
+
+        public CharSequence inflateUsing(Context context, Cursor cursor) {
+            final int index = mColumnName != null ? cursor.getColumnIndex(mColumnName) : -1;
+            final boolean validString = mStringRes > 0;
+            final boolean validColumn = index != -1;
+
+            final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+            final CharSequence columnValue = validColumn ? cursor.getString(index) : null;
+
+            if (validString && validColumn) {
+                return String.format(stringValue.toString(), columnValue);
+            } else if (validString) {
+                return stringValue;
+            } else if (validColumn) {
+                return columnValue;
+            } else {
+                return null;
+            }
+        }
+
+        public CharSequence inflateUsing(Context context, ContentValues values) {
+            final boolean validColumn = values.containsKey(mColumnName);
+            final boolean validString = mStringRes > 0;
+
+            final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+            final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
+
+            if (validString && validColumn) {
+                return String.format(stringValue.toString(), columnValue);
+            } else if (validString) {
+                return stringValue;
+            } else if (validColumn) {
+                return columnValue;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    public static abstract class CommonInflater implements StringInflater {
+        protected abstract int getTypeLabelResource(Integer type);
+
+        protected boolean isCustom(Integer type) {
+            return type == BaseTypes.TYPE_CUSTOM;
+        }
+
+        protected CharSequence getTypeLabel(Resources res, Integer type, CharSequence label) {
+            final int labelRes = getTypeLabelResource(type);
+            if (type == null) {
+                return res.getText(labelRes);
+            } else if (isCustom(type)) {
+                return res.getString(labelRes, label == null ? "" : label);
+            } else {
+                return res.getText(labelRes);
+            }
+        }
+
+        public CharSequence inflateUsing(Context context, Cursor cursor) {
+            final Integer type = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
+            final String label = cursor.getString(cursor.getColumnIndex(Phone.LABEL));
+            return getTypeLabel(context.getResources(), type, label);
+        }
+
+        public CharSequence inflateUsing(Context context, ContentValues values) {
+            final Integer type = values.getAsInteger(Phone.TYPE);
+            final String label = values.getAsString(Phone.LABEL);
+            return getTypeLabel(context.getResources(), type, label);
+        }
+    }
+
+    public static class PhoneActionInflater extends CommonInflater {
+        @Override
+        protected boolean isCustom(Integer type) {
+            return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
+        }
+
+        @Override
+        protected int getTypeLabelResource(Integer type) {
+            if (type == null) return R.string.call_other;
+            switch (type) {
+                case Phone.TYPE_HOME: return R.string.call_home;
+                case Phone.TYPE_MOBILE: return R.string.call_mobile;
+                case Phone.TYPE_WORK: return R.string.call_work;
+                case Phone.TYPE_FAX_WORK: return R.string.call_fax_work;
+                case Phone.TYPE_FAX_HOME: return R.string.call_fax_home;
+                case Phone.TYPE_PAGER: return R.string.call_pager;
+                case Phone.TYPE_OTHER: return R.string.call_other;
+                case Phone.TYPE_CALLBACK: return R.string.call_callback;
+                case Phone.TYPE_CAR: return R.string.call_car;
+                case Phone.TYPE_COMPANY_MAIN: return R.string.call_company_main;
+                case Phone.TYPE_ISDN: return R.string.call_isdn;
+                case Phone.TYPE_MAIN: return R.string.call_main;
+                case Phone.TYPE_OTHER_FAX: return R.string.call_other_fax;
+                case Phone.TYPE_RADIO: return R.string.call_radio;
+                case Phone.TYPE_TELEX: return R.string.call_telex;
+                case Phone.TYPE_TTY_TDD: return R.string.call_tty_tdd;
+                case Phone.TYPE_WORK_MOBILE: return R.string.call_work_mobile;
+                case Phone.TYPE_WORK_PAGER: return R.string.call_work_pager;
+                case Phone.TYPE_ASSISTANT: return R.string.call_assistant;
+                case Phone.TYPE_MMS: return R.string.call_mms;
+                default: return R.string.call_custom;
+            }
+        }
+    }
+
+    public static class PhoneActionAltInflater extends CommonInflater {
+        @Override
+        protected boolean isCustom(Integer type) {
+            return (type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT);
+        }
+
+        @Override
+        protected int getTypeLabelResource(Integer type) {
+            if (type == null) return R.string.sms_other;
+            switch (type) {
+                case Phone.TYPE_HOME: return R.string.sms_home;
+                case Phone.TYPE_MOBILE: return R.string.sms_mobile;
+                case Phone.TYPE_WORK: return R.string.sms_work;
+                case Phone.TYPE_FAX_WORK: return R.string.sms_fax_work;
+                case Phone.TYPE_FAX_HOME: return R.string.sms_fax_home;
+                case Phone.TYPE_PAGER: return R.string.sms_pager;
+                case Phone.TYPE_OTHER: return R.string.sms_other;
+                case Phone.TYPE_CALLBACK: return R.string.sms_callback;
+                case Phone.TYPE_CAR: return R.string.sms_car;
+                case Phone.TYPE_COMPANY_MAIN: return R.string.sms_company_main;
+                case Phone.TYPE_ISDN: return R.string.sms_isdn;
+                case Phone.TYPE_MAIN: return R.string.sms_main;
+                case Phone.TYPE_OTHER_FAX: return R.string.sms_other_fax;
+                case Phone.TYPE_RADIO: return R.string.sms_radio;
+                case Phone.TYPE_TELEX: return R.string.sms_telex;
+                case Phone.TYPE_TTY_TDD: return R.string.sms_tty_tdd;
+                case Phone.TYPE_WORK_MOBILE: return R.string.sms_work_mobile;
+                case Phone.TYPE_WORK_PAGER: return R.string.sms_work_pager;
+                case Phone.TYPE_ASSISTANT: return R.string.sms_assistant;
+                case Phone.TYPE_MMS: return R.string.sms_mms;
+                default: return R.string.sms_custom;
+            }
+        }
+    }
+
+    public static class EmailActionInflater extends CommonInflater {
+        @Override
+        protected int getTypeLabelResource(Integer type) {
+            if (type == null) return R.string.email;
+            switch (type) {
+                case Email.TYPE_HOME: return R.string.email_home;
+                case Email.TYPE_WORK: return R.string.email_work;
+                case Email.TYPE_OTHER: return R.string.email_other;
+                case Email.TYPE_MOBILE: return R.string.email_mobile;
+                default: return R.string.email_custom;
+            }
+        }
+    }
+
+    public static class PostalActionInflater extends CommonInflater {
+        @Override
+        protected int getTypeLabelResource(Integer type) {
+            if (type == null) return R.string.map_other;
+            switch (type) {
+                case StructuredPostal.TYPE_HOME: return R.string.map_home;
+                case StructuredPostal.TYPE_WORK: return R.string.map_work;
+                case StructuredPostal.TYPE_OTHER: return R.string.map_other;
+                default: return R.string.map_custom;
+            }
+        }
+    }
+
+    public static class ImActionInflater extends CommonInflater {
+        @Override
+        protected int getTypeLabelResource(Integer type) {
+            if (type == null) return R.string.chat;
+            switch (type) {
+                case Im.PROTOCOL_AIM: return R.string.chat_aim;
+                case Im.PROTOCOL_MSN: return R.string.chat_msn;
+                case Im.PROTOCOL_YAHOO: return R.string.chat_yahoo;
+                case Im.PROTOCOL_SKYPE: return R.string.chat_skype;
+                case Im.PROTOCOL_QQ: return R.string.chat_qq;
+                case Im.PROTOCOL_GOOGLE_TALK: return R.string.chat_gtalk;
+                case Im.PROTOCOL_ICQ: return R.string.chat_icq;
+                case Im.PROTOCOL_JABBER: return R.string.chat_jabber;
+                case Im.PROTOCOL_NETMEETING: return R.string.chat;
+                default: return R.string.chat;
+            }
+        }
+    }
+}
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
new file mode 100644
index 0000000..46d0623
--- /dev/null
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -0,0 +1,247 @@
+/*
+ * 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.model;
+
+import com.android.contacts.R;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts.Data;
+
+import java.util.ArrayList;
+
+public class GoogleSource extends FallbackSource {
+    public static final String ACCOUNT_TYPE = "com.google.GAIA";
+
+    public GoogleSource(String resPackageName) {
+        this.accountType = ACCOUNT_TYPE;
+        this.resPackageName = null;
+        this.summaryResPackageName = resPackageName;
+    }
+
+    @Override
+    protected void inflate(Context context, int inflateLevel) {
+
+        inflateStructuredName(inflateLevel);
+        inflateNickname(inflateLevel);
+        inflatePhone(inflateLevel);
+        inflateEmail(inflateLevel);
+        inflateStructuredPostal(inflateLevel);
+        inflateIm(inflateLevel);
+        inflateOrganization(inflateLevel);
+        inflatePhoto(inflateLevel);
+        inflateNote(inflateLevel);
+        inflateWebsite(inflateLevel);
+
+        // TODO: GOOGLE: GROUPMEMBERSHIP
+
+        setInflatedLevel(inflateLevel);
+
+    }
+
+    @Override
+    protected DataKind inflateStructuredName(int inflateLevel) {
+        return super.inflateStructuredName(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateNickname(int inflateLevel) {
+        return super.inflateNickname(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflatePhone(int inflateLevel) {
+        final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Phone.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+                    Phone.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateEmail(int inflateLevel) {
+        final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+        if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            kind.typeColumn = Email.TYPE;
+            kind.typeList = Lists.newArrayList();
+            kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+            kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+            kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+            kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+                    Email.LABEL));
+
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+        }
+
+        return kind;
+    }
+
+    @Override
+    protected DataKind inflateStructuredPostal(int inflateLevel) {
+        return super.inflateStructuredPostal(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateIm(int inflateLevel) {
+        return super.inflateIm(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateOrganization(int inflateLevel) {
+        return super.inflateOrganization(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflatePhoto(int inflateLevel) {
+        return super.inflatePhoto(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateNote(int inflateLevel) {
+        return super.inflateNote(inflateLevel);
+    }
+
+    @Override
+    protected DataKind inflateWebsite(int inflateLevel) {
+        return super.inflateWebsite(inflateLevel);
+    }
+
+    // TODO: this should come from resource in the future
+    private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
+
+    public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
+        attemptMyContactsMembership(state, context, true);
+    }
+
+    /**
+     *
+     * @param allowRecur If the group is created between querying/about to create, we recur.  But
+     *     to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
+     *     once
+     */
+    private static final void attemptMyContactsMembership(EntityDelta state, Context context,
+            boolean allowRecur) {
+        final ContentResolver resolver = context.getContentResolver();
+        final ValuesDelta stateValues = state.getValues();
+        final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
+        final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
+
+        Cursor cursor = resolver.query(Groups.CONTENT_URI,
+                new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
+                Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
+                new String[] {accountName, accountType}, null);
+
+        boolean myContactsExists = false;
+        long assignToGroupSourceId = -1;
+        while (cursor.moveToNext()) {
+            if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
+                myContactsExists = true;
+            }
+            if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
+                assignToGroupSourceId = cursor.getInt(1);
+            }
+
+            if (myContactsExists && assignToGroupSourceId != -1) {
+                break;
+            }
+        }
+
+        try {
+            final ContentValues values = new ContentValues();
+            values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+
+            if (!myContactsExists) {
+                // create the group if it doesn't exist
+                final ContentValues newGroup = new ContentValues();
+                newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
+
+                newGroup.put(Groups.ACCOUNT_NAME, accountName);
+                newGroup.put(Groups.ACCOUNT_TYPE, accountType);
+                newGroup.put(Groups.GROUP_VISIBLE, "1");
+
+                ArrayList<ContentProviderOperation> operations =
+                    new ArrayList<ContentProviderOperation>();
+
+                operations.add(ContentProviderOperation
+                        .newAssertQuery(Groups.CONTENT_URI)
+                        .withSelection(Groups.TITLE + "=?",
+                                new String[] { GOOGLE_MY_CONTACTS_GROUP })
+                        .withExpectedCount(0).build());
+                operations.add(ContentProviderOperation
+
+                        .newInsert(Groups.CONTENT_URI)
+                        .withValues(newGroup)
+                        .build());
+                try {
+                    ContentProviderResult[] results = resolver.applyBatch(
+                            ContactsContract.AUTHORITY, operations);
+                    values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
+                } catch (RemoteException e) {
+                    throw new IllegalStateException("Problem querying for groups", e);
+                } catch (OperationApplicationException e) {
+                    // the group was created after the query but before we tried to create it
+                    if (allowRecur) {
+                        attemptMyContactsMembership(state, context, false);
+                    }
+                    return;
+                }
+            } else {
+                if (assignToGroupSourceId != -1) {
+                    values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
+                } else {
+                    // there are no Groups to add this contact to, so don't apply any membership
+                    // TODO: alert user that their contact will be dropped?
+                }
+            }
+            state.addEntry(ValuesDelta.fromAfter(values));
+        } finally {
+            cursor.close();
+        }
+    }
+}
diff --git a/src/com/android/contacts/model/HardCodedSources.java b/src/com/android/contacts/model/HardCodedSources.java
deleted file mode 100644
index 503eed8..0000000
--- a/src/com/android/contacts/model/HardCodedSources.java
+++ /dev/null
@@ -1,1009 +0,0 @@
-/*
- * 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.model;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.provider.ContactsContract.Contacts.Data;
-import android.view.inputmethod.EditorInfo;
-
-import com.google.android.collect.Lists;
-
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.model.ContactsSource.EditField;
-import com.android.contacts.model.ContactsSource.EditType;
-import com.android.contacts.model.ContactsSource.StringInflater;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-
-import java.util.ArrayList;
-
-/**
- * Hard-coded definition of some {@link ContactsSource} constraints, since the
- * XML language hasn't been finalized.
- */
-public class HardCodedSources {
-    // TODO: finish hard-coding all constraints
-
-    public static final String ACCOUNT_TYPE_GOOGLE = "com.google.GAIA";
-    public static final String ACCOUNT_TYPE_EXCHANGE = "com.android.exchange";
-    public static final String ACCOUNT_TYPE_FACEBOOK = "com.facebook.auth.login";
-    public static final String ACCOUNT_TYPE_FALLBACK = "com.example.fallback-contacts";
-
-    private static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
-    private static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
-    private static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
-    private static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
-    private static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
-    private static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
-    private static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_VARIATION_URI;
-    private static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
-            | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
-            | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
-
-    private HardCodedSources() {
-        // Static utility class
-    }
-
-    /**
-     * Hard-coded instance of {@link ContactsSource} for fallback use.
-     */
-    static void buildFallback(Context context, ContactsSource list) {
-        {
-            // FALLBACK: STRUCTUREDNAME
-            DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                    R.string.nameLabelsGroup, -1, -1, true);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
-                    FLAGS_PERSON_NAME));
-
-            list.add(kind);
-        }
-
-        {
-            // FALLBACK: PHONE
-            DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
-                    R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
-            kind.iconAltRes = R.drawable.sym_action_sms;
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
-            kind.typeColumn = Phone.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
-                    R.string.sms_home));
-            kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
-                    R.string.call_mobile, R.string.sms_mobile));
-            kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
-                    R.string.sms_work));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
-                    R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
-                    R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
-                    R.string.call_pager, R.string.sms_pager).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
-                    R.string.call_other, R.string.sms_other));
-            kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_custom,
-                    R.string.call_custom, R.string.sms_custom).setSecondary(true).setCustomColumn(
-                    Phone.LABEL));
-            kind.typeList.add(new EditType(Phone.TYPE_CAR, R.string.type_car, R.string.call_car,
-                    R.string.sms_car).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_COMPANY_MAIN, R.string.type_company_main,
-                    R.string.call_company_main, R.string.sms_company_main).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_MMS, R.string.type_mms, R.string.call_mms,
-                    R.string.sms_mms).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_RADIO, R.string.type_radio, R.string.call_radio,
-                    R.string.sms_radio).setSecondary(true));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
-            list.add(kind);
-        }
-
-        {
-            // FALLBACK: POSTAL
-            DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
-                    R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            // TODO: build body from various structured fields
-            kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
-            kind.typeColumn = StructuredPostal.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
-                    R.string.map_home));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
-                    R.string.map_work));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
-                    R.string.map_other));
-            kind.typeList
-                    .add(new EditType(StructuredPostal.TYPE_CUSTOM, R.string.type_custom,
-                            R.string.map_custom).setSecondary(true).setCustomColumn(
-                            StructuredPostal.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
-                    FLAGS_POSTAL, true));
-            kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
-                    R.string.postal_neighborhood, FLAGS_POSTAL, true));
-            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
-                    FLAGS_POSTAL, true));
-
-            list.add(kind);
-        }
-
-        {
-            // FALLBACK: EMAIL
-            DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
-                    R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Email.DATA);
-
-            kind.typeColumn = Email.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList
-                    .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
-            kind.typeList
-                    .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
-            kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
-                    R.string.email_other));
-            kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
-                    R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-    }
-
-    /**
-     * Hard-coded instance of {@link ContactsSource} for Google Contacts.
-     */
-    static void buildGoogle(Context context, ContactsSource list) {
-        {
-            // GOOGLE: STRUCTUREDNAME
-            DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                    R.string.nameLabelsGroup, -1, -1, true);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                    R.string.name_phonetic_given, FLAGS_PHONETIC, true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
-                    R.string.name_phonetic_middle, FLAGS_PHONETIC, true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                    R.string.name_phonetic_family, FLAGS_PHONETIC, true));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: PHOTO
-            DataKind kind = new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: PHONE
-            DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
-                    R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
-            kind.iconAltRes = R.drawable.sym_action_sms;
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
-            kind.typeColumn = Phone.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
-                    R.string.sms_home));
-            kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
-                    R.string.call_mobile, R.string.sms_mobile));
-            kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
-                    R.string.sms_work));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
-                    R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
-                    R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
-                    R.string.call_pager, R.string.sms_pager).setSecondary(true));
-            kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
-                    R.string.call_other, R.string.sms_other));
-            kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_custom,
-                    R.string.call_custom, R.string.sms_custom).setSecondary(true).setCustomColumn(
-                    Phone.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: EMAIL
-            DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
-                    R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Email.DATA);
-
-            kind.typeColumn = Email.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList
-                    .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
-            kind.typeList
-                    .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
-            kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
-                    R.string.email_other));
-            kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
-                    R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: IM
-            DataKind kind = new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
-                    android.R.drawable.sym_action_chat, 20, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Im.DATA);
-
-            // NOTE: even though a traditional "type" exists, for editing
-            // purposes we're using the protocol to pick labels
-
-            kind.defaultValues = new ContentValues();
-            kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
-
-            kind.typeColumn = Im.PROTOCOL;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Im.PROTOCOL_AIM, R.string.type_im_aim,
-                    R.string.chat_aim));
-            kind.typeList.add(new EditType(Im.PROTOCOL_MSN, R.string.type_im_msn,
-                    R.string.chat_msn));
-            kind.typeList.add(new EditType(Im.PROTOCOL_YAHOO, R.string.type_im_yahoo,
-                    R.string.chat_yahoo));
-            kind.typeList.add(new EditType(Im.PROTOCOL_SKYPE, R.string.type_im_skype,
-                    R.string.chat_skype));
-            kind.typeList.add(new EditType(Im.PROTOCOL_QQ, R.string.type_im_qq, R.string.chat_qq));
-            kind.typeList.add(new EditType(Im.PROTOCOL_GOOGLE_TALK, R.string.type_im_google_talk,
-                    R.string.chat_gtalk));
-            kind.typeList.add(new EditType(Im.PROTOCOL_ICQ, R.string.type_im_icq,
-                    R.string.chat_icq));
-            kind.typeList.add(new EditType(Im.PROTOCOL_JABBER, R.string.type_im_jabber,
-                    R.string.chat_jabber));
-            kind.typeList.add(new EditType(Im.PROTOCOL_CUSTOM, R.string.type_custom,
-                    R.string.chat_other).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: POSTAL
-            DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
-                    R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            // TODO: build body from various structured fields
-            kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
-            kind.typeColumn = StructuredPostal.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
-                    R.string.map_home));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
-                    R.string.map_work));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
-                    R.string.map_other));
-            kind.typeList
-                    .add(new EditType(StructuredPostal.TYPE_CUSTOM, R.string.type_custom,
-                            R.string.map_custom).setSecondary(true).setCustomColumn(
-                            StructuredPostal.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
-                    FLAGS_POSTAL, true));
-            kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
-                    R.string.postal_neighborhood, FLAGS_POSTAL, true));
-            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
-                    FLAGS_POSTAL, true));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: ORGANIZATION
-            DataKind kind = new DataKind(Organization.CONTENT_ITEM_TYPE,
-                    R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true);
-
-            kind.actionHeader = new SimpleInflater(Organization.COMPANY);
-            // TODO: build body from multiple fields
-            kind.actionBody = new SimpleInflater(Organization.TITLE);
-
-            kind.typeColumn = Organization.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Organization.TYPE_WORK, R.string.type_work));
-            kind.typeList.add(new EditType(Organization.TYPE_OTHER, R.string.type_other));
-            kind.typeList.add(new EditType(Organization.TYPE_CUSTOM, R.string.type_custom)
-                    .setSecondary(true).setCustomColumn(Organization.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
-                    FLAGS_GENERIC_NAME));
-            kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
-                    FLAGS_GENERIC_NAME));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: NOTE
-            DataKind kind = new DataKind(Note.CONTENT_ITEM_TYPE,
-                    R.string.label_notes, R.drawable.sym_note, 110, true);
-            kind.secondary = true;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.label_notes);
-            kind.actionBody = new SimpleInflater(Note.NOTE);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
-
-            list.add(kind);
-        }
-
-        {
-            // GOOGLE: NICKNAME
-            DataKind kind = new DataKind(Nickname.CONTENT_ITEM_TYPE,
-                    R.string.nicknameLabelsGroup, -1, 115, true);
-            kind.secondary = true;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.nicknameLabelsGroup);
-            kind.actionBody = new SimpleInflater(Nickname.NAME);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
-                    FLAGS_PERSON_NAME));
-
-            list.add(kind);
-        }
-
-        // TODO: GOOGLE: GROUPMEMBERSHIP
-
-        {
-            // GOOGLE: WEBSITE
-            DataKind kind = new DataKind(Website.CONTENT_ITEM_TYPE,
-                    R.string.websiteLabelsGroup, -1, 120, true);
-            kind.secondary = true;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.websiteLabelsGroup);
-            kind.actionBody = new SimpleInflater(Website.URL);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
-
-            list.add(kind);
-        }
-    }
-
-    // TODO: this should come from resource in the future
-    private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
-    public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
-        attemptMyContactsMembership(state, context, true);
-    }
-
-    /**
-     *
-     * @param allowRecur If the group is created between querying/about to create, we recur.  But
-     *     to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
-     *     once
-     */
-    private static final void attemptMyContactsMembership(EntityDelta state, Context context,
-            boolean allowRecur) {
-        final ContentResolver resolver = context.getContentResolver();
-        final ValuesDelta stateValues = state.getValues();
-        final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
-        final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
-
-        Cursor cursor = resolver.query(Groups.CONTENT_URI,
-                new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
-                Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
-                new String[] {accountName, accountType}, null);
-
-        boolean myContactsExists = false;
-        long assignToGroupSourceId = -1;
-        while (cursor.moveToNext()) {
-            if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
-                myContactsExists = true;
-            }
-            if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
-                assignToGroupSourceId = cursor.getInt(1);
-            }
-
-            if (myContactsExists && assignToGroupSourceId != -1) {
-                break;
-            }
-        }
-
-        try {
-            final ContentValues values = new ContentValues();
-            values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
-
-            if (!myContactsExists) {
-                // create the group if it doesn't exist
-                final ContentValues newGroup = new ContentValues();
-                newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
-
-                newGroup.put(Groups.ACCOUNT_NAME, accountName);
-                newGroup.put(Groups.ACCOUNT_TYPE, accountType);
-                newGroup.put(Groups.GROUP_VISIBLE, "1");
-
-                ArrayList<ContentProviderOperation> operations =
-                    new ArrayList<ContentProviderOperation>();
-
-                operations.add(ContentProviderOperation
-                        .newAssertQuery(Groups.CONTENT_URI)
-                        .withSelection(Groups.TITLE + "=?",
-                                new String[] { GOOGLE_MY_CONTACTS_GROUP })
-                        .withExpectedCount(0).build());
-                operations.add(ContentProviderOperation
-
-                        .newInsert(Groups.CONTENT_URI)
-                        .withValues(newGroup)
-                        .build());
-                try {
-                    ContentProviderResult[] results = resolver.applyBatch(
-                            ContactsContract.AUTHORITY, operations);
-                    values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
-                } catch (RemoteException e) {
-                    throw new IllegalStateException("Problem querying for groups", e);
-                } catch (OperationApplicationException e) {
-                    // the group was created after the query but before we tried to create it
-                    if (allowRecur) {
-                        attemptMyContactsMembership(state, context, false);
-                    }
-                    return;
-                }
-            } else {
-                if (assignToGroupSourceId != -1) {
-                    values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
-                } else {
-                    // there are no Groups to add this contact to, so don't apply any membership
-                    // TODO: alert user that their contact will be dropped?
-                }
-            }
-            state.addEntry(ValuesDelta.fromAfter(values));
-        } finally {
-            cursor.close();
-        }
-    }
-
-    /**
-     * Hard-coded instance of {@link ContactsSource} for Exchange.
-     */
-    static void buildExchange(Context context, ContactsSource list) {
-        {
-            // EXCHANGE: STRUCTUREDNAME
-            DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                    R.string.nameLabelsGroup, -1, -1, true);
-            kind.typeOverallMax = 1;
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
-                    FLAGS_PERSON_NAME, true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                    R.string.name_phonetic_given, FLAGS_PHONETIC, true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                    R.string.name_phonetic_family, FLAGS_PHONETIC, true));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: PHOTO
-            DataKind kind = new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true);
-            kind.typeOverallMax = 1;
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: PHONE
-            DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
-                    R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
-            kind.iconAltRes = R.drawable.sym_action_sms;
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
-            kind.typeColumn = Phone.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
-                    R.string.sms_home).setSpecificMax(2));
-            kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
-                    R.string.call_mobile, R.string.sms_mobile).setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
-                    R.string.sms_work).setSpecificMax(2));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
-                    R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true)
-                    .setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
-                    R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true)
-                    .setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
-                    R.string.call_pager, R.string.sms_pager).setSecondary(true).setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_CAR, R.string.type_car, R.string.call_car,
-                    R.string.sms_car).setSecondary(true).setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_COMPANY_MAIN, R.string.type_company_main,
-                    R.string.call_company_main, R.string.sms_company_main).setSecondary(true)
-                    .setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_MMS, R.string.type_mms, R.string.call_mms,
-                    R.string.sms_mms).setSecondary(true).setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_RADIO, R.string.type_radio,
-                    R.string.call_radio, R.string.sms_radio).setSecondary(true).setSpecificMax(1));
-            kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_assistant,
-                    R.string.call_custom, R.string.sms_custom).setSecondary(true).setSpecificMax(1)
-                    .setCustomColumn(Phone.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: EMAIL
-            DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
-                    R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Email.DATA);
-            kind.typeOverallMax = 3;
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: IM
-            DataKind kind = new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
-                    android.R.drawable.sym_action_chat, 20, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Im.DATA);
-            kind.typeOverallMax = 3;
-
-            // NOTE: even though a traditional "type" exists, for editing
-            // purposes we're using the protocol to pick labels
-
-            kind.defaultValues = new ContentValues();
-            kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
-
-            kind.typeColumn = Im.PROTOCOL;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Im.PROTOCOL_AIM, R.string.type_im_aim,
-                    R.string.chat_aim));
-            kind.typeList.add(new EditType(Im.PROTOCOL_MSN, R.string.type_im_msn,
-                    R.string.chat_msn));
-            kind.typeList.add(new EditType(Im.PROTOCOL_YAHOO, R.string.type_im_yahoo,
-                    R.string.chat_yahoo));
-            kind.typeList.add(new EditType(Im.PROTOCOL_SKYPE, R.string.type_im_skype,
-                    R.string.chat_skype));
-            kind.typeList.add(new EditType(Im.PROTOCOL_QQ, R.string.type_im_qq, R.string.chat_qq));
-            kind.typeList.add(new EditType(Im.PROTOCOL_GOOGLE_TALK, R.string.type_im_google_talk,
-                    R.string.chat_gtalk));
-            kind.typeList.add(new EditType(Im.PROTOCOL_ICQ, R.string.type_im_icq,
-                    R.string.chat_icq));
-            kind.typeList.add(new EditType(Im.PROTOCOL_JABBER, R.string.type_im_jabber,
-                    R.string.chat_jabber));
-            kind.typeList.add(new EditType(Im.PROTOCOL_CUSTOM, R.string.type_custom,
-                    R.string.chat_other).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: POSTAL
-            DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
-                    R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            // TODO: build body from various structured fields
-            kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
-            kind.typeColumn = StructuredPostal.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
-                    R.string.map_work).setSpecificMax(1));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
-                    R.string.map_home).setSpecificMax(1));
-            kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
-                    R.string.map_other).setSpecificMax(1));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
-                    FLAGS_POSTAL, true));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: NICKNAME
-            DataKind kind = new DataKind(Nickname.CONTENT_ITEM_TYPE,
-                    R.string.nicknameLabelsGroup, -1, 115, true);
-            kind.secondary = true;
-            kind.typeOverallMax = 1;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.nicknameLabelsGroup);
-            kind.actionBody = new SimpleInflater(Nickname.NAME);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
-                    FLAGS_PERSON_NAME));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: WEBSITE
-            DataKind kind = new DataKind(Website.CONTENT_ITEM_TYPE,
-                    R.string.websiteLabelsGroup, -1, 120, true);
-            kind.secondary = true;
-            kind.typeOverallMax = 1;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.websiteLabelsGroup);
-            kind.actionBody = new SimpleInflater(Website.URL);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: ORGANIZATION
-            DataKind kind = new DataKind(Organization.CONTENT_ITEM_TYPE,
-                    R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true);
-
-            kind.actionHeader = new SimpleInflater(Organization.COMPANY);
-            // TODO: build body from multiple fields
-            kind.actionBody = new SimpleInflater(Organization.TITLE);
-
-            kind.typeColumn = Organization.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Organization.TYPE_WORK, R.string.type_work));
-            kind.typeList.add(new EditType(Organization.TYPE_OTHER, R.string.type_other));
-            kind.typeList.add(new EditType(Organization.TYPE_CUSTOM, R.string.type_custom)
-                    .setSecondary(true).setCustomColumn(Organization.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
-                    FLAGS_GENERIC_NAME));
-            kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
-                    FLAGS_GENERIC_NAME));
-
-            list.add(kind);
-        }
-
-        {
-            // EXCHANGE: NOTE
-            DataKind kind = new DataKind(Note.CONTENT_ITEM_TYPE,
-                    R.string.label_notes, R.drawable.sym_note, 110, true);
-            kind.secondary = true;
-
-            kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.label_notes);
-            kind.actionBody = new SimpleInflater(Note.NOTE);
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
-
-            list.add(kind);
-        }
-    }
-
-    /**
-     * Hard-coded instance of {@link ContactsSource} for Facebook.
-     */
-    static void buildFacebook(Context context, ContactsSource list) {
-        list.accountType = ACCOUNT_TYPE_FACEBOOK;
-        list.readOnly = true;
-
-        {
-            // FACEBOOK: PHONE
-            DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
-                    R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
-            kind.iconAltRes = R.drawable.sym_action_sms;
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
-            kind.typeColumn = Phone.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
-                    R.string.call_mobile, R.string.sms_mobile));
-            kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
-                    R.string.call_other, R.string.sms_other));
-
-            list.add(kind);
-        }
-
-        {
-            // FACEBOOK: EMAIL
-            DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
-                    R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
-            kind.actionHeader = new ActionInflater(list.resPackageName, kind);
-            kind.actionBody = new SimpleInflater(Email.DATA);
-
-            kind.typeColumn = Email.TYPE;
-            kind.typeList = Lists.newArrayList();
-            kind.typeList
-                    .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
-            kind.typeList
-                    .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
-            kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
-                    R.string.email_other));
-            kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
-                    R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
-            kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
-            list.add(kind);
-        }
-    }
-
-    /**
-     * Simple inflater that assumes a string resource has a "%s" that will be
-     * filled from the given column.
-     */
-    public static class SimpleInflater implements StringInflater {
-        private final String mPackageName;
-        private final int mStringRes;
-        private final String mColumnName;
-
-        public SimpleInflater(String packageName, int stringRes) {
-            this(packageName, stringRes, null);
-        }
-
-        public SimpleInflater(String columnName) {
-            this(null, -1, columnName);
-        }
-
-        public SimpleInflater(String packageName, int stringRes, String columnName) {
-            mPackageName = packageName;
-            mStringRes = stringRes;
-            mColumnName = columnName;
-        }
-
-        public CharSequence inflateUsing(Context context, Cursor cursor) {
-            final int index = mColumnName != null ? cursor.getColumnIndex(mColumnName) : -1;
-            final boolean validString = mStringRes > 0;
-            final boolean validColumn = index != -1;
-
-            final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
-            final CharSequence columnValue = validColumn ? cursor.getString(index) : null;
-
-            if (validString && validColumn) {
-                return String.format(stringValue.toString(), columnValue);
-            } else if (validString) {
-                return stringValue;
-            } else if (validColumn) {
-                return columnValue;
-            } else {
-                return null;
-            }
-        }
-
-        public CharSequence inflateUsing(Context context, ContentValues values) {
-            final boolean validColumn = values.containsKey(mColumnName);
-            final boolean validString = mStringRes > 0;
-
-            final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
-            final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
-
-            if (validString && validColumn) {
-                return String.format(stringValue.toString(), columnValue);
-            } else if (validString) {
-                return stringValue;
-            } else if (validColumn) {
-                return columnValue;
-            } else {
-                return null;
-            }
-        }
-    }
-
-    /**
-     * Simple inflater that will combine two string resources, usually to
-     * provide an action string like "Call home", where "home" is provided from
-     * {@link EditType#labelRes}.
-     */
-    public static class ActionInflater implements StringInflater {
-        private String mPackageName;
-        private DataKind mKind;
-
-        public ActionInflater(String packageName, DataKind labelProvider) {
-            mPackageName = packageName;
-            mKind = labelProvider;
-        }
-
-        public CharSequence inflateUsing(Context context, Cursor cursor) {
-            final EditType type = EntityModifier.getCurrentType(cursor, mKind);
-            final boolean validString = (type != null && type.actionRes != 0);
-            if (!validString) return null;
-
-            if (type.customColumn != null) {
-                final int index = cursor.getColumnIndex(type.customColumn);
-                final String customLabel = cursor.getString(index);
-                return String.format(context.getString(type.actionRes), customLabel);
-            } else {
-                return context.getText(type.actionRes);
-            }
-        }
-
-        public CharSequence inflateUsing(Context context, ContentValues values) {
-            final EditType type = EntityModifier.getCurrentType(values, mKind);
-            final boolean validString = (type != null && type.actionRes != 0);
-            if (!validString) return null;
-
-            if (type.customColumn != null) {
-                final String customLabel = values.getAsString(type.customColumn);
-                return String.format(context.getString(type.actionRes), customLabel);
-            } else {
-                return context.getText(type.actionRes);
-            }
-        }
-    }
-
-    public static class ActionAltInflater implements StringInflater {
-        private String mPackageName;
-        private DataKind mKind;
-
-        public ActionAltInflater(String packageName, DataKind labelProvider) {
-            mPackageName = packageName;
-            mKind = labelProvider;
-        }
-
-        public CharSequence inflateUsing(Context context, Cursor cursor) {
-            final EditType type = EntityModifier.getCurrentType(cursor, mKind);
-            final boolean validString = (type != null && type.actionAltRes != 0);
-            CharSequence actionString;
-            if (type.customColumn != null) {
-                final int index = cursor.getColumnIndex(type.customColumn);
-                final String customLabel = cursor.getString(index);
-                actionString = String.format(context.getString(type.actionAltRes),
-                        customLabel);
-            } else {
-                actionString = context.getText(type.actionAltRes);
-            }
-            return validString ? actionString : null;
-        }
-
-        public CharSequence inflateUsing(Context context, ContentValues values) {
-            final EditType type = EntityModifier.getCurrentType(values, mKind);
-            final boolean validString = (type != null && type.actionAltRes != 0);
-            CharSequence actionString;
-            if (type.customColumn != null) {
-                final String customLabel = values.getAsString(type.customColumn);
-                actionString = String.format(context.getString(type.actionAltRes),
-                        customLabel);
-            } else {
-                actionString = context.getText(type.actionAltRes);
-            }
-            return validString ? actionString : null;
-        }
-    }
-}
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index 71607c7..ad9ddf1 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -16,38 +16,45 @@
 
 package com.android.contacts.model;
 
-import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource.DataKind;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorDescription;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentService;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SyncAdapterType;
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 
 /**
  * Singleton holder for all parsed {@link ContactsSource} available on the
  * system, typically filled through {@link PackageManager} queries.
  */
-public class Sources {
+public class Sources extends BroadcastReceiver {
     private static final String TAG = "Sources";
 
-    public static final String ACCOUNT_TYPE_FALLBACK = HardCodedSources.ACCOUNT_TYPE_FALLBACK;
-
     private Context mContext;
 
+    private ContactsSource mFallbackSource = null;
+
     private HashMap<String, ContactsSource> mSources = Maps.newHashMap();
+    private HashSet<String> mKnownPackages = Sets.newHashSet();
 
     private static SoftReference<Sources> sInstance = null;
 
@@ -70,13 +77,58 @@
      */
     private Sources(Context context) {
         mContext = context;
+
+        // Create fallback contacts source for on-phone contacts
+        mFallbackSource = new FallbackSource();
+
         loadAccounts();
+        registerIntentReceivers(context);
     }
 
     /** @hide exposed for unit tests */
     public Sources(ContactsSource... sources) {
         for (ContactsSource source : sources) {
-            mSources.put(source.accountType, source);
+            addSource(source);
+        }
+    }
+
+    protected void addSource(ContactsSource source) {
+        mSources.put(source.accountType, source);
+        mKnownPackages.add(source.resPackageName);
+    }
+
+    private void registerIntentReceivers(Context context) {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+
+        // We use getApplicationContext() so that the broadcast reciever can stay registered for
+        // the length of the application lifetime (instead of the calling activity's lifetime).
+        // This is so that we can notified of package changes, and purge the cache accordingly,
+        // but not be woken up if the application process isn't already running, since we will
+        // have no cache to clear at that point.
+        context.getApplicationContext().registerReceiver(this, filter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        final String packageName = intent.getData().getSchemeSpecificPart();
+
+        final boolean matchingPackage = mKnownPackages.contains(packageName);
+        final boolean validAction = Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                || Intent.ACTION_PACKAGE_ADDED.equals(action)
+                || Intent.ACTION_PACKAGE_CHANGED.equals(action);
+
+        if (matchingPackage && validAction) {
+            for (ContactsSource source : mSources.values()) {
+                if (TextUtils.equals(packageName, source.resPackageName)) {
+                    // Invalidate any cache for the changed package
+                    source.invalidateCache();
+                }
+            }
         }
     }
 
@@ -87,17 +139,6 @@
     protected void loadAccounts() {
         mSources.clear();
 
-        {
-            // Create fallback contacts source for on-phone contacts
-            final ContactsSource source = new ContactsSource();
-            source.accountType = HardCodedSources.ACCOUNT_TYPE_FALLBACK;
-            source.resPackageName = mContext.getPackageName();
-            source.titleRes = R.string.account_phone;
-            source.iconRes = R.drawable.ic_launcher_contacts;
-
-            mSources.put(source.accountType, source);
-        }
-
         final AccountManager am = AccountManager.get(mContext);
         final IContentService cs = ContentResolver.getContentService();
 
@@ -116,14 +157,23 @@
                 final String accountType = sync.accountType;
                 final AuthenticatorDescription auth = findAuthenticator(auths, accountType);
 
-                final ContactsSource source = new ContactsSource();
+                ContactsSource source;
+                if (GoogleSource.ACCOUNT_TYPE.equals(accountType)) {
+                    source = new GoogleSource(auth.packageName);
+                } else if (ExchangeSource.ACCOUNT_TYPE.equals(accountType)) {
+                    source = new ExchangeSource(auth.packageName);
+                } else {
+                    // TODO: use syncadapter package instead, since it provides resources
+                    Log.d(TAG, "Creating external source for type=" + accountType
+                            + ", packageName=" + auth.packageName);
+                    source = new ExternalSource(auth.packageName);
+                }
+
                 source.accountType = auth.type;
-                // TODO: use syncadapter package instead, since it provides resources
-                source.resPackageName = auth.packageName;
                 source.titleRes = auth.labelId;
                 source.iconRes = auth.iconId;
 
-                mSources.put(accountType, source);
+                addSource(source);
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Problem loading accounts: " + e.toString());
@@ -166,22 +216,45 @@
         return matching;
     }
 
-    protected ContactsSource getSourceForType(String accountType) {
-        ContactsSource source = mSources.get(accountType);
-        if (source == null) {
-            Log.w(TAG, "Unknown account type '" + accountType + "', falling back to default");
-            source = mSources.get(ACCOUNT_TYPE_FALLBACK);
+    /**
+     * Find the best {@link DataKind} matching the requested
+     * {@link ContactsSource#accountType} and {@link DataKind#mimeType}. If no
+     * direct match found, we try searching {@link #mFallbackSource}.
+     */
+    public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
+            int inflateLevel) {
+        DataKind kind = null;
+
+        // Try finding source and kind matching request
+        final ContactsSource source = mSources.get(accountType);
+        if (source != null) {
+            source.ensureInflated(context, inflateLevel);
+            kind = source.getKindForMimetype(mimeType);
         }
-        return source;
+
+        if (kind == null) {
+            // Nothing found, so try fallback as last resort
+            mFallbackSource.ensureInflated(context, inflateLevel);
+            kind = mFallbackSource.getKindForMimetype(mimeType);
+        }
+
+        if (kind == null) {
+            Log.w(TAG, "Unknown type=" + accountType + ", mime=" + mimeType);
+        }
+
+        return kind;
     }
 
     /**
      * Return {@link ContactsSource} for the given account type.
      */
     public ContactsSource getInflatedSource(String accountType, int inflateLevel) {
-        final ContactsSource source = getSourceForType(accountType);
-        if (source == null || source.isInflated(inflateLevel)) {
-            // Found inflated, so return directly
+        // Try finding specific source, otherwise use fallback
+        ContactsSource source = mSources.get(accountType);
+        if (source == null) source = mFallbackSource;
+
+        if (source.isInflated(inflateLevel)) {
+            // Already inflated, so return directly
             return source;
         } else {
             // Not inflated, but requested that we force-inflate
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 6059e62..c02ee80 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -62,12 +62,12 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.ScrollingTabWidget;
+import com.android.contacts.model.GoogleSource;
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Editor;
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityModifier;
 import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.HardCodedSources;
 import com.android.contacts.model.Sources;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
@@ -843,8 +843,8 @@
 
             // Create "My Contacts" membership for Google contacts
             // TODO: move this off into "templates" for each given source
-            if (HardCodedSources.ACCOUNT_TYPE_GOOGLE.equals(source.accountType)) {
-                HardCodedSources.attemptMyContactsMembership(insert, target);
+            if (GoogleSource.ACCOUNT_TYPE.equals(source.accountType)) {
+                GoogleSource.attemptMyContactsMembership(insert, target);
             }
 
 	    // TODO: no synchronization here on target.mState.  This
diff --git a/src/com/android/contacts/ui/FastTrackActivity.java b/src/com/android/contacts/ui/FastTrackActivity.java
index 8fdc40d..6958298 100644
--- a/src/com/android/contacts/ui/FastTrackActivity.java
+++ b/src/com/android/contacts/ui/FastTrackActivity.java
@@ -35,6 +35,10 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        if (FastTrackWindow.TRACE_LAUNCH) {
+            android.os.Debug.startMethodTracing(FastTrackWindow.TRACE_TAG);
+        }
+
         // Use our local window token for now
         final Intent intent = getIntent();
         final Uri lookupUri = intent.getData();
diff --git a/src/com/android/contacts/ui/FastTrackWindow.java b/src/com/android/contacts/ui/FastTrackWindow.java
index bad8a21..8626818 100644
--- a/src/com/android/contacts/ui/FastTrackWindow.java
+++ b/src/com/android/contacts/ui/FastTrackWindow.java
@@ -20,6 +20,7 @@
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
 import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.ui.widget.CheckableImageView;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.NotifyingAsyncQueryHandler;
 import com.android.internal.policy.PolicyManager;
@@ -39,14 +40,12 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.FastTrack;
-import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.Presence;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.SocialContract.Activities;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
@@ -133,7 +132,7 @@
     private View mFooter;
     private View mFooterDisambig;
     private ListView mResolveList;
-    private CheckableImageView mLastChiclet;
+    private CheckableImageView mLastAction;
 
     /**
      * Set of {@link Action} that are associated with the aggregate currently
@@ -159,6 +158,9 @@
     private static final int TOKEN_SOCIAL = 2;
     private static final int TOKEN_DATA = 3;
 
+    static final boolean TRACE_LAUNCH = false;
+    static final String TRACE_TAG = "fasttrack";
+
     /**
      * Prepare a fast-track window to show in the given {@link Context}.
      */
@@ -245,6 +247,10 @@
             return;
         }
 
+        if (TRACE_LAUNCH && !android.os.Debug.isMethodTracingActive()) {
+            android.os.Debug.startMethodTracing(TRACE_TAG);
+        }
+
         // Prepare header view for requested mode
         mMode = mode;
         mHeader = getHeaderView(mode);
@@ -330,6 +336,10 @@
         mQuerying = false;
 
         mTrack.startAnimation(mTrackAnim);
+
+        if (TRACE_LAUNCH) {
+            android.os.Debug.stopMethodTracing();
+        }
     }
 
     /**
@@ -342,7 +352,7 @@
         }
 
         boolean hadDecor = mDecor != null;
-        
+
         if (hadDecor) {
             mWindowManager.removeView(mDecor);
             mDecor = null;
@@ -350,7 +360,7 @@
         }
 
         // Release refrence to last chiclet.
-        mLastChiclet = null;
+        mLastAction = null;
 
         // Completely hide header from current mode
         mHeader.setVisibility(View.GONE);
@@ -367,7 +377,7 @@
         mTrackScroll.fullScroll(View.FOCUS_LEFT);
         mWasDownArrow = false;
 
-        setResolveVisible(false);
+        setResolveVisible(false, null);
 
         mQuerying = false;
         mHasSummary = false;
@@ -380,7 +390,7 @@
         }
 
         mShowing = false;
-        
+
         // Notify any listeners that we've been dismissed
         if (mDismissListener != null) {
             mDismissListener.onDismiss(this);
@@ -576,7 +586,6 @@
      */
     private static class DataAction implements Action {
         private final Context mContext;
-        private final ContactsSource mSource;
         private final DataKind mKind;
         private final String mMimeType;
 
@@ -589,10 +598,8 @@
         /**
          * Create an action from common {@link Data} elements.
          */
-        public DataAction(Context context, ContactsSource source, String mimeType, DataKind kind,
-                Cursor cursor) {
+        public DataAction(Context context, String mimeType, DataKind kind, Cursor cursor) {
             mContext = context;
-            mSource = source;
             mKind = kind;
             mMimeType = mimeType;
 
@@ -656,13 +663,14 @@
         /** {@inheritDoc} */
         public Drawable getFallbackIcon() {
             // Bail early if no valid resources
-            if (mSource.resPackageName == null) return null;
+            final String resPackageName = mKind.resPackageName;
+            if (resPackageName == null) return null;
 
             final PackageManager pm = mContext.getPackageManager();
-            if (mAlternate && mKind.iconAltRes > 0) {
-                return pm.getDrawable(mSource.resPackageName, mKind.iconAltRes, null);
-            } else if (mKind.iconRes > 0) {
-                return pm.getDrawable(mSource.resPackageName, mKind.iconRes, null);
+            if (mAlternate && mKind.iconAltRes != -1) {
+                return pm.getDrawable(resPackageName, mKind.iconAltRes, null);
+            } else if (mKind.iconRes != -1) {
+                return pm.getDrawable(resPackageName, mKind.iconRes, null);
             } else {
                 return null;
             }
@@ -880,22 +888,21 @@
             // TODO: find the ContactsSource for this, either from accountType,
             // or through lazy-loading when resPackage is set, or default.
 
-            final ContactsSource source = sources.getInflatedSource(accountType,
+            final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
                     ContactsSource.LEVEL_MIMETYPES);
-            final DataKind kind = source.getKindForMimetype(mimeType);
 
             if (kind != null) {
                 // Build an action for this data entry, find a mapping to a UI
                 // element, build its summary from the cursor, and collect it
                 // along with all others of this MIME-type.
-                final Action action = new DataAction(mContext, source, mimeType, kind, cursor);
+                final Action action = new DataAction(mContext, mimeType, kind, cursor);
                 considerAdd(action, mimeType);
             }
 
             // If phone number, also insert as text message action
             if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && kind != null) {
-                final Action action = new DataAction(mContext, source, Constants.MIME_SMS_ADDRESS,
-                        kind, cursor);
+                final Action action = new DataAction(mContext, Constants.MIME_SMS_ADDRESS, kind,
+                        cursor);
                 considerAdd(action, Constants.MIME_SMS_ADDRESS);
             }
         }
@@ -972,10 +979,14 @@
      * Helper for showing and hiding {@link #mFooterDisambig}, which will
      * correctly manage {@link #mArrowDown} as needed.
      */
-    private void setResolveVisible(boolean visible) {
+    private void setResolveVisible(boolean visible, CheckableImageView actionView) {
         // Show or hide the resolve list if needed
         boolean visibleNow = mFooterDisambig.getVisibility() == View.VISIBLE;
 
+        if (mLastAction != null) mLastAction.setChecked(false);
+        if (actionView != null) actionView.setChecked(true);
+        mLastAction = actionView;
+
         // Bail early if already in desired state
         if (visible == visibleNow) return;
 
@@ -986,21 +997,21 @@
             // If showing list, then hide and save state of down arrow
             mWasDownArrow = mWasDownArrow || (mArrowDown.getVisibility() == View.VISIBLE);
             mArrowDown.setVisibility(View.INVISIBLE);
-	    mLastChiclet.setChecked(true);
         } else {
             // If hiding list, restore any down arrow state
             mArrowDown.setVisibility(mWasDownArrow ? View.VISIBLE : View.INVISIBLE);
-	    mLastChiclet.setChecked(false);
         }
     }
 
     /** {@inheritDoc} */
-    public void onClick(View v) {
-        mLastChiclet = (CheckableImageView)v;
-        final Object tag = v.getTag();
+    public void onClick(View view) {
+        final boolean isActionView = (view instanceof CheckableImageView);
+        final CheckableImageView actionView = isActionView ? (CheckableImageView)view : null;
+
+        final Object tag = view.getTag();
         if (tag instanceof Intent) {
             // Hide the resolution list, if present
-            setResolveVisible(false);
+            setResolveVisible(false, actionView);
             this.dismiss();
 
             try {
@@ -1014,7 +1025,7 @@
             final ActionList children = (ActionList)tag;
 
             // Show resolution list and set adapter
-            setResolveVisible(true);
+            setResolveVisible(true, actionView);
 
             mResolveList.setOnItemClickListener(this);
             mResolveList.setAdapter(new BaseAdapter() {
@@ -1063,7 +1074,7 @@
             // Back key will first dismiss any expanded resolve list, otherwise
             // it will close the entire dialog.
             if (mFooterDisambig.getVisibility() == View.VISIBLE) {
-                setResolveVisible(false);
+                setResolveVisible(false, null);
             } else {
                 dismiss();
             }
@@ -1205,6 +1216,7 @@
                 Data.MIMETYPE,
                 Data.IS_PRIMARY,
                 Data.IS_SUPER_PRIMARY,
+                Data.RAW_CONTACT_ID,
                 Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
                 Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11,
                 Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
diff --git a/src/com/android/contacts/ui/CheckableImageView.java b/src/com/android/contacts/ui/widget/CheckableImageView.java
similarity index 97%
rename from src/com/android/contacts/ui/CheckableImageView.java
rename to src/com/android/contacts/ui/widget/CheckableImageView.java
index 257e407..ceddf57 100644
--- a/src/com/android/contacts/ui/CheckableImageView.java
+++ b/src/com/android/contacts/ui/widget/CheckableImageView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.ui;
+package com.android.contacts.ui.widget;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -49,7 +49,7 @@
     public void toggle() {
         setChecked(!mChecked);
     }
-    
+
     public boolean isChecked() {
         return mChecked;
     }
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index dd40634..6bc3005 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -33,6 +33,7 @@
 
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Entity;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
@@ -67,16 +68,12 @@
         mContext = getContext();
     }
 
-    /**
-     * Build a {@link ContactsSource} that has various odd constraints for
-     * testing purposes.
-     */
-    protected ContactsSource getSource() {
-        final ContactsSource list = new ContactsSource();
-        list.accountType = TEST_ACCOUNT_TYPE;
-        list.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);
+    public static class MockContactsSource extends ContactsSource {
+        @Override
+        protected void inflate(Context context, int inflateLevel) {
+            this.accountType = TEST_ACCOUNT_TYPE;
+            this.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);
 
-        {
             // Phone allows maximum 2 home, 1 work, and unlimited other, with
             // constraint of 5 numbers maximum.
             DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true);
@@ -93,10 +90,16 @@
             kind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
             kind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
 
-            list.add(kind);
+            addKind(kind);
         }
+    }
 
-        return list;
+    /**
+     * Build a {@link ContactsSource} that has various odd constraints for
+     * testing purposes.
+     */
+    protected ContactsSource getSource() {
+        return new MockContactsSource();
     }
 
     /**
diff --git a/tests/src/com/android/contacts/StyleManagerTests.java b/tests/src/com/android/contacts/StyleManagerTests.java
deleted file mode 100644
index aaf54be..0000000
--- a/tests/src/com/android/contacts/StyleManagerTests.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import java.util.Arrays;
-
-import com.android.contacts.StyleManager;
-import com.android.contacts.tests.R;
-
-/**
- * Tests for the StyleManager class.
- */
-@LargeTest
-public class StyleManagerTests extends AndroidTestCase {
-
-    public static final String LOG_TAG = "StyleManagerTests";
-
-    private StyleManager mStyleManager;
-    private static final String PACKAGE_NAME = "com.android.contacts.tests";
-    private static final String PHONE_MIMETYPE = "vnd.android.cursor.item/phone";
-    private Context mContext;
-
-    public StyleManagerTests() {
-        super();
-    }
-
-    @Override
-    public void setUp() {
-        mContext = getContext();
-        mStyleManager = StyleManager.getInstance(mContext);
-    }
-
-    public void testGetMimetypeIcon() {
-        Bitmap phoneIconFromSm = mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME, PHONE_MIMETYPE);
-        int smHeight = phoneIconFromSm.getHeight();
-        int smWidth = phoneIconFromSm.getWidth();
-
-        Bitmap phoneIconFromRes = BitmapFactory.decodeResource(mContext.getResources(),
-                R.drawable.phone_icon, null);
-        int resHeight = phoneIconFromRes.getHeight();
-        int resWidth = phoneIconFromRes.getWidth();
-
-        int[] smPixels = new int[smWidth*smHeight];
-        phoneIconFromSm.getPixels(smPixels, 0, smWidth, 0, 0, smWidth, smHeight);
-
-        int[] resPixels = new int[resWidth*resHeight];
-        phoneIconFromRes.getPixels(resPixels, 0, resWidth, 0, 0, resWidth, resHeight);
-
-        assertTrue(Arrays.equals(smPixels, resPixels));
-    }
-
-    public void testGetMissingMimetypeIcon() {
-        Bitmap postalIconFromSm = mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME,
-                "vnd.android.cursor.item/postal-address");
-
-        assertNull(postalIconFromSm);
-    }
-
-    public void testGetDefaultIcon() {
-        Bitmap defaultIconFromSm = mStyleManager.getDefaultIcon(mContext, PACKAGE_NAME);
-
-        int smHeight = defaultIconFromSm.getHeight();
-        int smWidth = defaultIconFromSm.getWidth();
-
-        Bitmap defaultIconFromRes = BitmapFactory.decodeResource(mContext.getResources(),
-                R.drawable.default_icon, null);
-        int resHeight = defaultIconFromRes.getHeight();
-        int resWidth = defaultIconFromRes.getWidth();
-
-        int[] smPixels = new int[smWidth*smHeight];
-        defaultIconFromSm.getPixels(smPixels, 0, smWidth, 0, 0, smWidth, smHeight);
-
-        int[] resPixels = new int[resWidth*resHeight];
-        defaultIconFromRes.getPixels(resPixels, 0, resWidth, 0, 0, resWidth, resHeight);
-
-        assertTrue(Arrays.equals(smPixels, resPixels));
-    }
-
-    public void testCaching() {
-        // Clear cache
-        mStyleManager.onPackageChange(PACKAGE_NAME);
-        assertTrue(mStyleManager.getIconCacheSize() == 0);
-        assertTrue(mStyleManager.getStyleSetCacheSize() == 0);
-
-        // Getting the icon should add it to the cache.
-        mStyleManager.getDefaultIcon(mContext, PACKAGE_NAME);
-        assertTrue(mStyleManager.getIconCacheSize() == 1);
-        assertTrue(mStyleManager.getStyleSetCacheSize() == 1);
-        assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
-        assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
-        assertTrue(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
-
-        mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME, PHONE_MIMETYPE);
-        assertTrue(mStyleManager.getIconCacheSize() == 2);
-        assertTrue(mStyleManager.getStyleSetCacheSize() == 1);
-        assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
-        assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
-        assertTrue(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
-
-        // Clear cache
-        mStyleManager.onPackageChange(PACKAGE_NAME);
-        assertTrue(mStyleManager.getIconCacheSize() == 0);
-        assertTrue(mStyleManager.getStyleSetCacheSize() == 0);
-        assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
-        assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
-        assertFalse(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
-    }
-
-}