Follow up to EditSchema parser

- Now AccountType.addKind() throws DefinitionException instead of just logging.

- Now the test contacts.xml (= test_basic_contacts.xml) defines "event" and
"relationship" DataKinds too.  BUA should be able to copy this file.

- Added another xml, contacts_fallback.xml, to the test apk.
This defines what's equivalent to the fallback type.  Unittests load this file
directly and compares the result to the fallback account type.

- Cleaned up existing account definitions in order to make sure
contacts_fallback.xml is really identical to the fallback type. This includes:

** Now structured name, display name, phonetic name, and phone DataKinds
all have 'kind.typeOverallMax = 1'.

** The "assistant" phone type is no longer a custom column.  It's only used for
the fallback type and I don't think it's too critical.

- Also, NameKindBuilder no longer re-order phonetic fields, because no
other account types do this.  In the previous CL I did it because I thought
that'd be more "correct", but on the second thought it's probably not a good
idea to make too many non-critical changes at this point.

Bug 5381810

Change-Id: Ie6a4eb3b876ab22a3dcdb6a9c278e387f8166125
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 654adeb..23d39d3 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -32,7 +32,6 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
-import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
 
@@ -83,11 +82,35 @@
      */
     private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
 
+    protected boolean mIsInitialized;
+
+    protected static class DefinitionException extends Exception {
+        public DefinitionException(String message) {
+            super(message);
+        }
+
+        public DefinitionException(String message, Exception inner) {
+            super(message, inner);
+        }
+    }
+
     /**
      * Whether this account type was able to be fully initialized.  This may be false if
      * (for example) the package name associated with the account type could not be found.
      */
-    public boolean isInitialized() {
+    public final boolean isInitialized() {
+        return mIsInitialized;
+    }
+
+    /**
+     * @return Whether this type is an "embedded" type.  i.e. any of {@link FallbackAccountType},
+     * {@link GoogleAccountType} or {@link ExternalAccountType}.
+     *
+     * If an embedded type cannot be initialized (i.e. if {@link #isInitialized()} returns
+     * {@code false}) it's considered critical, and the application will crash.  On the other
+     * hand if it's not an embedded type, we just skip loading the type.
+     */
+    public boolean isEmbedded() {
         return true;
     }
 
@@ -274,10 +297,10 @@
     /**
      * Add given {@link DataKind} to list of those provided by this source.
      */
-    public DataKind addKind(DataKind kind) {
+    public DataKind addKind(DataKind kind) throws DefinitionException {
         if (mMimeKinds.get(kind.mimeType) != null) {
-            // TODO Make it exception.
-            Log.w(TAG, "mime type '" + kind.mimeType + "' is already registered");
+            throw new DefinitionException(
+                    "mime type '" + kind.mimeType + "' is already registered");
         }
 
         kind.resPackageName = this.resPackageName;
@@ -337,6 +360,16 @@
         public int hashCode() {
             return rawValue;
         }
+
+        @Override
+        public String toString() {
+            return this.getClass().getSimpleName()
+                    + " rawValue=" + rawValue
+                    + " labelRes=" + labelRes
+                    + " secondary=" + secondary
+                    + " specificMax=" + specificMax
+                    + " customColumn=" + customColumn;
+        }
     }
 
     public static class EventEditType extends EditType {
@@ -354,6 +387,11 @@
             mYearOptional = yearOptional;
             return this;
         }
+
+        @Override
+        public String toString() {
+            return super.toString() + " mYearOptional=" + mYearOptional;
+        }
     }
 
     /**
@@ -403,6 +441,19 @@
         public boolean isMultiLine() {
             return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
         }
+
+
+        @Override
+        public String toString() {
+            return this.getClass().getSimpleName() + ":"
+                    + " column=" + column
+                    + " titleRes=" + titleRes
+                    + " inputType=" + inputType
+                    + " minLines=" + minLines
+                    + " optional=" + optional
+                    + " shortForm=" + shortForm
+                    + " longForm=" + longForm;
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 92323fa..43bb579 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -411,7 +411,12 @@
                     Log.d(TAG, "Registering external account type=" + type
                             + ", packageName=" + auth.packageName);
                     accountType = new ExternalAccountType(mContext, auth.packageName, false);
-                    if (!accountType.isInitialized()) {
+                }
+                if (!accountType.isInitialized()) {
+                    if (accountType.isEmbedded()) {
+                        throw new IllegalStateException("Problem initializing embedded type "
+                                + accountType.getClass().getCanonicalName());
+                    } else {
                         // Skip external account types that couldn't be initialized.
                         continue;
                     }
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index 34beca2..0d766da 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -17,6 +17,7 @@
 package com.android.contacts.model;
 
 import com.android.contacts.R;
+import com.android.contacts.model.AccountType.DefinitionException;
 import com.android.contacts.util.DateUtils;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
@@ -107,16 +108,6 @@
         static final int GROUP_MEMBERSHIP = 999;
     }
 
-    protected static class DefinitionException extends Exception {
-        public DefinitionException(String message) {
-            super(message);
-        }
-
-        public DefinitionException(String message, Exception inner) {
-            super(message, inner);
-        }
-    }
-
     public BaseAccountType() {
         this.accountType = null;
         this.dataSet = null;
@@ -148,11 +139,12 @@
         return new EditType(type, Relation.getTypeLabelResource(type));
     }
 
-    protected DataKind addDataKindStructuredName(Context context) {
+    protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
                 R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
@@ -177,11 +169,12 @@
         return kind;
     }
 
-    protected DataKind addDataKindDisplayName(Context context) {
+    protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
                 R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
@@ -217,11 +210,12 @@
         return kind;
     }
 
-    protected DataKind addDataKindPhoneticName(Context context) {
+    protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
                 R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME,
@@ -236,7 +230,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindNickname(Context context) {
+    protected DataKind addDataKindNickname(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
                     R.string.nicknameLabelsGroup, 115, true, R.layout.text_fields_editor_view));
         kind.typeOverallMax = 1;
@@ -252,7 +246,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindPhone(Context context) {
+    protected DataKind addDataKindPhone(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
                 10, true, R.layout.text_fields_editor_view));
         kind.iconAltRes = R.drawable.ic_text_holo_light;
@@ -262,8 +256,8 @@
         kind.actionBody = new SimpleInflater(Phone.NUMBER);
         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_HOME));
         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));
@@ -282,8 +276,7 @@
         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_ASSISTANT).setSecondary(true));
         kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));
 
         kind.fieldList = Lists.newArrayList();
@@ -292,7 +285,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindEmail(Context context) {
+    protected DataKind addDataKindEmail(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
                 15, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new EmailActionInflater();
@@ -312,7 +305,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindStructuredPostal(Context context) {
+    protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
                 R.string.postalLabelsGroup, 25, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new PostalActionInflater();
@@ -333,7 +326,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindIm(Context context) {
+    protected DataKind addDataKindIm(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20, true,
                     R.layout.text_fields_editor_view));
         kind.actionHeader = new ImActionInflater();
@@ -364,7 +357,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindOrganization(Context context) {
+    protected DataKind addDataKindOrganization(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
                     R.string.organizationLabelsGroup, 5, true,
                     R.layout.text_fields_editor_view));
@@ -381,14 +374,15 @@
         return kind;
     }
 
-    protected DataKind addDataKindPhoto(Context context) {
+    protected DataKind addDataKindPhoto(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true, -1));
+        kind.typeOverallMax = 1;
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
         return kind;
     }
 
-    protected DataKind addDataKindNote(Context context) {
+    protected DataKind addDataKindNote(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
                     R.string.label_notes, 110, true, R.layout.text_fields_editor_view));
         kind.typeOverallMax = 1;
@@ -400,7 +394,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindWebsite(Context context) {
+    protected DataKind addDataKindWebsite(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
                 R.string.websiteLabelsGroup, 120, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
@@ -414,7 +408,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindSipAddress(Context context) {
+    protected DataKind addDataKindSipAddress(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
                     R.string.label_sip_address, 130, true, R.layout.text_fields_editor_view));
 
@@ -428,7 +422,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindGroupMembership(Context context) {
+    protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
                 R.string.groupsLabel, 999, true, -1));
 
@@ -496,6 +490,13 @@
                 return null;
             }
         }
+
+        @Override
+        public String toString() {
+            return this.getClass().getSimpleName()
+                    + " mStringRes=" + mStringRes
+                    + " mColumnName" + mColumnName;
+        }
     }
 
     public static abstract class CommonInflater implements StringInflater {
@@ -535,6 +536,11 @@
             final String label = values.getAsString(getLabelColumn());
             return getTypeLabel(context.getResources(), type, label);
         }
+
+        @Override
+        public String toString() {
+            return this.getClass().getSimpleName();
+        }
     }
 
     public static class PhoneActionInflater extends CommonInflater {
@@ -801,15 +807,14 @@
             kind.actionBody = actionBody;
             kind.fieldList = Lists.newArrayList();
 
-            kind.typeOverallMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
-
-            // Handle "types".
-            // If a kind has the type column, contacts.xml must have at least one type definition.
-            // Otherwise, it mustn't have a type definition.
-            //
-            // If it's a pseudo data kind (== data kind that doesn't have the corresponding
-            // DataKind tag in the XML), we just skip this process.
+            // Get more information from the tag...
+            // A pseudo data kind doesn't have corresponding tag the XML, so we skip this.
             if (!isPseudo) {
+                kind.typeOverallMax = getAttr(attrs, Attr.MAX_OCCURRENCE, -1);
+
+                // Process "Type" tags.
+                // If a kind has the type column, contacts.xml must have at least one type
+                // definition.  Otherwise, it mustn't have a type definition.
                 if (kind.typeColumn != null) {
                     // Parse and add types.
                     kind.typeList = Lists.newArrayList();
@@ -957,7 +962,6 @@
             throwIfList(ks);
             kinds.add(ks);
 
-
             // Note about setLongForm/setShortForm below.
             // We need to set this only when the type supports display name. (=supportsDisplayName)
             // Otherwise (i.e. Exchange) we don't set these flags, but instead make some fields
@@ -988,6 +992,7 @@
                     R.string.nameLabelsGroup, Weight.NONE, R.layout.text_fields_editor_view,
                     new SimpleInflater(R.string.nameLabelsGroup),
                     new SimpleInflater(Nickname.NAME));
+            kd.typeOverallMax = 1;
             kinds.add(kd);
 
             kd.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
@@ -1023,25 +1028,18 @@
                     R.string.name_phonetic, Weight.NONE, R.layout.phonetic_name_editor_view,
                     new SimpleInflater(R.string.nameLabelsGroup),
                     new SimpleInflater(Nickname.NAME));
+            kp.typeOverallMax = 1;
             kinds.add(kp);
 
+            // We may want to change the order depending on displayOrderPrimary too.
             kp.fieldList.add(new EditField(DataKind.PSEUDO_COLUMN_PHONETIC_NAME,
                     R.string.name_phonetic, FLAGS_PHONETIC).setShortForm(true));
-            if (!displayOrderPrimary) {
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                        R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
-                        R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                        R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
-            } else {
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                        R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
-                        R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
-                kp.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                        R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
-            }
+            kp.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                    R.string.name_phonetic_family, FLAGS_PHONETIC).setLongForm(true));
+            kp.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+                    R.string.name_phonetic_middle, FLAGS_PHONETIC).setLongForm(true));
+            kp.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                    R.string.name_phonetic_given, FLAGS_PHONETIC).setLongForm(true));
             return kinds;
         }
     }
diff --git a/src/com/android/contacts/model/DataKind.java b/src/com/android/contacts/model/DataKind.java
index aaf7bb5..857f3e4 100644
--- a/src/com/android/contacts/model/DataKind.java
+++ b/src/com/android/contacts/model/DataKind.java
@@ -20,6 +20,7 @@
 import com.android.contacts.model.AccountType.EditField;
 import com.android.contacts.model.AccountType.EditType;
 import com.android.contacts.model.AccountType.StringInflater;
+import com.google.common.collect.Iterators;
 
 import android.content.ContentValues;
 import android.provider.ContactsContract.Data;
@@ -95,4 +96,43 @@
         this.typeOverallMax = -1;
         this.editorLayoutResourceId = editorLayoutResourceId;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DataKind:");
+        sb.append(" resPackageName=").append(resPackageName);
+        sb.append(" mimeType=").append(mimeType);
+        sb.append(" titleRes=").append(titleRes);
+        sb.append(" iconAltRes=").append(iconAltRes);
+        sb.append(" iconAltDescriptionRes=").append(iconAltDescriptionRes);
+        sb.append(" weight=").append(weight);
+        sb.append(" editable=").append(editable);
+        sb.append(" actionHeader=").append(actionHeader);
+        sb.append(" actionAltHeader=").append(actionAltHeader);
+        sb.append(" actionBody=").append(actionBody);
+        sb.append(" actionBodySocial=").append(actionBodySocial);
+        sb.append(" typeColumn=").append(typeColumn);
+        sb.append(" typeOverallMax=").append(typeOverallMax);
+        sb.append(" typeList=").append(toString(typeList));
+        sb.append(" fieldList=").append(toString(fieldList));
+        sb.append(" defaultValues=").append(defaultValues);
+        sb.append(" editorLayoutResourceId=").append(editorLayoutResourceId);
+        sb.append(" dateFormatWithoutYear=").append(toString(dateFormatWithoutYear));
+        sb.append(" dateFormatWithYear=").append(toString(dateFormatWithYear));
+
+        return sb.toString();
+    }
+
+    public static String toString(SimpleDateFormat format) {
+        return format == null ? "(null)" : format.toPattern();
+    }
+
+    public static String toString(Iterable<?> list) {
+        if (list == null) {
+            return "(null)";
+        } else {
+            return Iterators.toString(list.iterator());
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/model/ExchangeAccountType.java b/src/com/android/contacts/model/ExchangeAccountType.java
index bb11cf6..e5491d2 100644
--- a/src/com/android/contacts/model/ExchangeAccountType.java
+++ b/src/com/android/contacts/model/ExchangeAccountType.java
@@ -17,6 +17,7 @@
 package com.android.contacts.model;
 
 import com.android.contacts.R;
+import com.android.contacts.model.AccountType.DefinitionException;
 import com.android.contacts.util.DateUtils;
 import com.google.android.collect.Lists;
 
@@ -33,10 +34,12 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.util.Log;
 
 import java.util.Locale;
 
 public class ExchangeAccountType extends BaseAccountType {
+    private static final String TAG = "ExchangeAccountType";
 
     public static final String ACCOUNT_TYPE = "com.android.exchange";
 
@@ -45,24 +48,30 @@
         this.resPackageName = null;
         this.summaryResPackageName = resPackageName;
 
-        addDataKindStructuredName(context);
-        addDataKindDisplayName(context);
-        addDataKindPhoneticName(context);
-        addDataKindNickname(context);
-        addDataKindPhone(context);
-        addDataKindEmail(context);
-        addDataKindStructuredPostal(context);
-        addDataKindIm(context);
-        addDataKindOrganization(context);
-        addDataKindPhoto(context);
-        addDataKindNote(context);
-        addDataKindEvent(context);
-        addDataKindWebsite(context);
-        addDataKindGroupMembership(context);
+        try {
+            addDataKindStructuredName(context);
+            addDataKindDisplayName(context);
+            addDataKindPhoneticName(context);
+            addDataKindNickname(context);
+            addDataKindPhone(context);
+            addDataKindEmail(context);
+            addDataKindStructuredPostal(context);
+            addDataKindIm(context);
+            addDataKindOrganization(context);
+            addDataKindPhoto(context);
+            addDataKindNote(context);
+            addDataKindEvent(context);
+            addDataKindWebsite(context);
+            addDataKindGroupMembership(context);
+
+            mIsInitialized = true;
+        } catch (DefinitionException e) {
+            Log.e(TAG, "Problem building account type", e);
+        }
     }
 
     @Override
-    protected DataKind addDataKindStructuredName(Context context) {
+    protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
                 R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
@@ -91,7 +100,7 @@
     }
 
     @Override
-    protected DataKind addDataKindDisplayName(Context context) {
+    protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
                 R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
 
@@ -124,7 +133,7 @@
     }
 
     @Override
-    protected DataKind addDataKindPhoneticName(Context context) {
+    protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
                 R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
@@ -142,7 +151,7 @@
     }
 
     @Override
-    protected DataKind addDataKindNickname(Context context) {
+    protected DataKind addDataKindNickname(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindNickname(context);
 
         kind.typeOverallMax = 1;
@@ -155,7 +164,7 @@
     }
 
     @Override
-    protected DataKind addDataKindPhone(Context context) {
+    protected DataKind addDataKindPhone(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindPhone(context);
 
         kind.typeColumn = Phone.TYPE;
@@ -185,7 +194,7 @@
     }
 
     @Override
-    protected DataKind addDataKindEmail(Context context) {
+    protected DataKind addDataKindEmail(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindEmail(context);
 
         kind.typeOverallMax = 3;
@@ -197,7 +206,7 @@
     }
 
     @Override
-    protected DataKind addDataKindStructuredPostal(Context context) {
+    protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindStructuredPostal(context);
 
         final boolean useJapaneseOrder =
@@ -237,7 +246,7 @@
     }
 
     @Override
-    protected DataKind addDataKindIm(Context context) {
+    protected DataKind addDataKindIm(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindIm(context);
 
         // Types are not supported for IM. There can be 3 IMs, but OWA only shows only the first
@@ -253,7 +262,7 @@
     }
 
     @Override
-    protected DataKind addDataKindOrganization(Context context) {
+    protected DataKind addDataKindOrganization(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindOrganization(context);
 
         kind.typeOverallMax = 1;
@@ -268,7 +277,7 @@
     }
 
     @Override
-    protected DataKind addDataKindPhoto(Context context) {
+    protected DataKind addDataKindPhoto(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindPhoto(context);
 
         kind.typeOverallMax = 1;
@@ -280,7 +289,7 @@
     }
 
     @Override
-    protected DataKind addDataKindNote(Context context) {
+    protected DataKind addDataKindNote(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindNote(context);
 
         kind.fieldList = Lists.newArrayList();
@@ -289,7 +298,7 @@
         return kind;
     }
 
-    protected DataKind addDataKindEvent(Context context) {
+    protected DataKind addDataKindEvent(Context context) throws DefinitionException {
         DataKind kind = addKind(
                 new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup, 150, true,
                 R.layout.event_field_editor_view));
@@ -311,7 +320,7 @@
     }
 
     @Override
-    protected DataKind addDataKindWebsite(Context context) {
+    protected DataKind addDataKindWebsite(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindWebsite(context);
 
         kind.typeOverallMax = 1;
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 968993a..4eaa976 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -16,8 +16,6 @@
 
 package com.android.contacts.model;
 
-import com.android.contacts.R;
-import com.android.contacts.model.BaseAccountType.DefinitionException;
 import com.google.common.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -88,30 +86,49 @@
     private List<String> mExtensionPackageNames;
     private String mAccountTypeLabelAttribute;
     private String mAccountTypeIconAttribute;
-    private boolean mInitSuccessful;
     private boolean mHasContactsMetadata;
     private boolean mHasEditSchema;
 
     public ExternalAccountType(Context context, String resPackageName, boolean isExtension) {
+        this(context, resPackageName, isExtension, null);
+    }
+
+    /**
+     * Constructor used for testing to initialize with any arbitrary XML.
+     *
+     * @param injectedMetadata If non-null, it'll be used to initialize the type.  Only set by
+     *     tests.  If null, the metadata is loaded from the specified package.
+     */
+    ExternalAccountType(Context context, String resPackageName, boolean isExtension,
+            XmlResourceParser injectedMetadata) {
         this.mIsExtension = isExtension;
         this.resPackageName = resPackageName;
         this.summaryResPackageName = resPackageName;
 
-        // Handle unknown sources by searching their package
         final PackageManager pm = context.getPackageManager();
-        XmlResourceParser parser = null;
+        final XmlResourceParser parser;
+        if (injectedMetadata == null) {
+            try {
+                parser = loadContactsXml(context, resPackageName);
+            } catch (NameNotFoundException e1) {
+                // If the package name is not found, we can't initialize this account type.
+                return;
+            }
+        } else {
+            parser = injectedMetadata;
+        }
         try {
-            PackageInfo packageInfo = pm.getPackageInfo(resPackageName,
-                    PackageManager.GET_SERVICES|PackageManager.GET_META_DATA);
-            for (ServiceInfo serviceInfo : packageInfo.services) {
-                parser = serviceInfo.loadXmlMetaData(pm,
-                        METADATA_CONTACTS);
-                if (parser == null) continue;
+            if (parser != null) {
                 inflate(context, parser);
             }
-        } catch (NameNotFoundException nnfe) {
-            // If the package name is not found, we can't initialize this account type.
-            return;
+
+            if (!mHasEditSchema) {
+                // Bring in name and photo from fallback source, which are non-optional
+                addDataKindStructuredName(context);
+                addDataKindDisplayName(context);
+                addDataKindPhoneticName(context);
+                addDataKindPhoto(context);
+            }
         } catch (DefinitionException e) {
             String message = "Problem reading XML";
             if (parser != null) {
@@ -135,16 +152,42 @@
         iconRes = resolveExternalResId(context, mAccountTypeIconAttribute,
                 this.resPackageName, ATTR_ACCOUNT_ICON);
 
-        if (!mHasEditSchema) {
-            // Bring in name and photo from fallback source, which are non-optional
-            addDataKindStructuredName(context);
-            addDataKindDisplayName(context);
-            addDataKindPhoneticName(context);
-            addDataKindPhoto(context);
-        }
-
         // If we reach this point, the account type has been successfully initialized.
-        mInitSuccessful = true;
+        mIsInitialized = true;
+    }
+
+    /**
+     * Returns the CONTACTS_STRUCTURE metadata (aka "contacts.xml") in the given apk package.
+     *
+     * Unfortunately, there's no public way to determine which service defines a sync service for
+     * which account type, so this method looks through all services in the package, and just
+     * returns the first CONTACTS_STRUCTURE metadata defined in any of them.
+     *
+     * Returns {@code null} if the package has no CONTACTS_STRUCTURE metadata.  In this case
+     * the account type *will* be initialized with minimal configuration.
+     *
+     * On the other hand, if the package is not found, it throws a {@link NameNotFoundException},
+     * in which case the account type will *not* be initialized.
+     */
+    private XmlResourceParser loadContactsXml(Context context, String resPackageName)
+            throws NameNotFoundException {
+        final PackageManager pm = context.getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(resPackageName,
+                PackageManager.GET_SERVICES|PackageManager.GET_META_DATA);
+        for (ServiceInfo serviceInfo : packageInfo.services) {
+            final XmlResourceParser parser = serviceInfo.loadXmlMetaData(pm,
+                    METADATA_CONTACTS);
+            if (parser != null) {
+                return parser;
+            }
+        }
+        // Package was found, but that doesn't contain the CONTACTS_STRUCTURE metadata.
+        return null;
+    }
+
+    @Override
+    public boolean isEmbedded() {
+        return false;
     }
 
     @Override
@@ -153,11 +196,6 @@
     }
 
     @Override
-    public boolean isInitialized() {
-        return mInitSuccessful;
-    }
-
-    @Override
     public boolean areContactsWritable() {
         return mHasEditSchema;
     }
diff --git a/src/com/android/contacts/model/FallbackAccountType.java b/src/com/android/contacts/model/FallbackAccountType.java
index 216d6d0..d81f2f5 100644
--- a/src/com/android/contacts/model/FallbackAccountType.java
+++ b/src/com/android/contacts/model/FallbackAccountType.java
@@ -19,31 +19,52 @@
 import com.android.contacts.R;
 
 import android.content.Context;
+import android.util.Log;
 
 public class FallbackAccountType extends BaseAccountType {
+    private static final String TAG = "FallbackAccountType";
 
-    public FallbackAccountType(Context context) {
+    private FallbackAccountType(Context context, String resPackageName) {
         this.accountType = null;
         this.dataSet = null;
         this.titleRes = R.string.account_phone;
         this.iconRes = R.mipmap.ic_launcher_contacts;
 
-        this.resPackageName = null;
+        this.resPackageName = resPackageName;
         this.summaryResPackageName = resPackageName;
 
-        addDataKindStructuredName(context);
-        addDataKindDisplayName(context);
-        addDataKindPhoneticName(context);
-        addDataKindNickname(context);
-        addDataKindPhone(context);
-        addDataKindEmail(context);
-        addDataKindStructuredPostal(context);
-        addDataKindIm(context);
-        addDataKindOrganization(context);
-        addDataKindPhoto(context);
-        addDataKindNote(context);
-        addDataKindWebsite(context);
-        addDataKindSipAddress(context);
+        try {
+            addDataKindStructuredName(context);
+            addDataKindDisplayName(context);
+            addDataKindPhoneticName(context);
+            addDataKindNickname(context);
+            addDataKindPhone(context);
+            addDataKindEmail(context);
+            addDataKindStructuredPostal(context);
+            addDataKindIm(context);
+            addDataKindOrganization(context);
+            addDataKindPhoto(context);
+            addDataKindNote(context);
+            addDataKindWebsite(context);
+            addDataKindSipAddress(context);
+
+            mIsInitialized = true;
+        } catch (DefinitionException e) {
+            Log.e(TAG, "Problem building account type", e);
+        }
+    }
+
+    public FallbackAccountType(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Used to compare with an {@link ExternalAccountType} built from a test contacts.xml.
+     * In order to build {@link DataKind}s with the same resource package name,
+     * {@code resPackageName} is injectable.
+     */
+    static AccountType createForTest(Context context, String resPackageName) {
+        return new FallbackAccountType(context, resPackageName);
     }
 
     @Override
diff --git a/src/com/android/contacts/model/GoogleAccountType.java b/src/com/android/contacts/model/GoogleAccountType.java
index c101602..822d829 100644
--- a/src/com/android/contacts/model/GoogleAccountType.java
+++ b/src/com/android/contacts/model/GoogleAccountType.java
@@ -17,6 +17,7 @@
 package com.android.contacts.model;
 
 import com.android.contacts.R;
+import com.android.contacts.model.AccountType.DefinitionException;
 import com.android.contacts.util.DateUtils;
 import com.google.android.collect.Lists;
 
@@ -26,10 +27,13 @@
 import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.util.Log;
 
 import java.util.List;
 
 public class GoogleAccountType extends BaseAccountType {
+    private static final String TAG = "GoogleAccountType";
+
     public static final String ACCOUNT_TYPE = "com.google";
 
     private static final List<String> mExtensionPackages =
@@ -40,22 +44,28 @@
         this.resPackageName = null;
         this.summaryResPackageName = resPackageName;
 
-        addDataKindStructuredName(context);
-        addDataKindDisplayName(context);
-        addDataKindPhoneticName(context);
-        addDataKindNickname(context);
-        addDataKindPhone(context);
-        addDataKindEmail(context);
-        addDataKindStructuredPostal(context);
-        addDataKindIm(context);
-        addDataKindOrganization(context);
-        addDataKindPhoto(context);
-        addDataKindNote(context);
-        addDataKindWebsite(context);
-        addDataKindSipAddress(context);
-        addDataKindGroupMembership(context);
-        addDataKindRelation(context);
-        addDataKindEvent(context);
+        try {
+            addDataKindStructuredName(context);
+            addDataKindDisplayName(context);
+            addDataKindPhoneticName(context);
+            addDataKindNickname(context);
+            addDataKindPhone(context);
+            addDataKindEmail(context);
+            addDataKindStructuredPostal(context);
+            addDataKindIm(context);
+            addDataKindOrganization(context);
+            addDataKindPhoto(context);
+            addDataKindNote(context);
+            addDataKindWebsite(context);
+            addDataKindSipAddress(context);
+            addDataKindGroupMembership(context);
+            addDataKindRelation(context);
+            addDataKindEvent(context);
+
+            mIsInitialized = true;
+        } catch (DefinitionException e) {
+            Log.e(TAG, "Problem building account type", e);
+        }
     }
 
     @Override
@@ -64,7 +74,7 @@
     }
 
     @Override
-    protected DataKind addDataKindPhone(Context context) {
+    protected DataKind addDataKindPhone(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindPhone(context);
 
         kind.typeColumn = Phone.TYPE;
@@ -87,7 +97,7 @@
     }
 
     @Override
-    protected DataKind addDataKindEmail(Context context) {
+    protected DataKind addDataKindEmail(Context context) throws DefinitionException {
         final DataKind kind = super.addDataKindEmail(context);
 
         kind.typeColumn = Email.TYPE;
@@ -104,7 +114,7 @@
         return kind;
     }
 
-    private DataKind addDataKindRelation(Context context) {
+    private DataKind addDataKindRelation(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Relation.CONTENT_ITEM_TYPE,
                 R.string.relationLabelsGroup, 160, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new RelationActionInflater();
@@ -139,7 +149,7 @@
         return kind;
     }
 
-    private DataKind addDataKindEvent(Context context) {
+    private DataKind addDataKindEvent(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
                     R.string.eventLabelsGroup, 150, true, R.layout.event_field_editor_view));
         kind.actionHeader = new EventActionInflater();
diff --git a/tests/res/xml/contacts_fallback.xml b/tests/res/xml/contacts_fallback.xml
new file mode 100644
index 0000000..ae262eb
--- /dev/null
+++ b/tests/res/xml/contacts_fallback.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<!--
+    contacts.xml to build "fallback account type" equivalent.
+    This is directly used in ExternalAccountTypeTest to test the parser.  There's no sync adapter
+    that actually defined with this definition.
+-->
+
+<ContactsAccountType
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+    <EditSchema
+        >
+        <DataKind kind="name"
+            maxOccurs="1"
+            supportsDisplayName="true"
+            supportsPrefix="true"
+            supportsMiddleName="true"
+            supportsSuffix="true"
+            supportsPhoneticFamilyName="true"
+            supportsPhoneticMiddleName="true"
+            supportsPhoneticGivenName="true"
+            >
+        </DataKind>
+        <DataKind kind="photo" maxOccurs="1" />
+        <DataKind kind="phone" >
+            <Type type="mobile" />
+            <Type type="home" />
+            <Type type="work" />
+            <Type type="fax_work" />
+            <Type type="fax_home" />
+            <Type type="pager" />
+            <Type type="other" />
+            <Type type="custom"/>
+            <Type type="callback" />
+            <Type type="car" />
+            <Type type="company_main" />
+            <Type type="isdn" />
+            <Type type="main" />
+            <Type type="other_fax" />
+            <Type type="radio" />
+            <Type type="telex" />
+            <Type type="tty_tdd" />
+            <Type type="work_mobile"/>
+            <Type type="work_pager" />
+            <Type type="assistant" />
+            <Type type="mms" />
+        </DataKind>
+        <DataKind kind="email" >
+            <Type type="home" />
+            <Type type="work" />
+            <Type type="other" />
+            <Type type="mobile" />
+            <Type type="custom" />
+        </DataKind>
+        <DataKind kind="nickname" maxOccurs="1" />
+        <DataKind kind="im" >
+            <Type type="aim" />
+            <Type type="msn" />
+            <Type type="yahoo" />
+            <Type type="skype" />
+            <Type type="qq" />
+            <Type type="google_talk" />
+            <Type type="icq" />
+            <Type type="jabber" />
+            <Type type="custom" />
+        </DataKind>
+        <DataKind kind="postal" needsStructured="false" >
+            <Type type="home" />
+            <Type type="work" />
+            <Type type="other" />
+            <Type type="custom" />
+        </DataKind>
+        <DataKind kind="organization" maxOccurs="1" />
+        <DataKind kind="website" />
+        <DataKind kind="sip_address" maxOccurs="1" />
+        <DataKind kind="note" maxOccurs="1" />
+    </EditSchema>
+</ContactsAccountType>
diff --git a/tests/res/xml/test_basic_contacts.xml b/tests/res/xml/test_basic_contacts.xml
index ad82706..0047204 100644
--- a/tests/res/xml/test_basic_contacts.xml
+++ b/tests/res/xml/test_basic_contacts.xml
@@ -17,10 +17,6 @@
  */
 -->
 
-<!--
-    contacts.xml to build "fallback account type" equivalent.
--->
-
 <ContactsAccountType
     xmlns:android="http://schemas.android.com/apk/res/android"
     >
@@ -98,8 +94,8 @@
         <DataKind kind="phone" >
             <!-- Note: Google type doesn't have obsolete ones -->
             <Type type="mobile" />
-            <Type type="work" />
             <Type type="home" />
+            <Type type="work" />
             <Type type="fax_work" />
             <Type type="fax_home" />
             <Type type="pager" />
@@ -139,9 +135,9 @@
         <!--
             Email
         -->
-        <!-- Fallback/ Google definition.  -->
+        <!-- Fallback/Google definition.  -->
         <DataKind kind="email" >
-            <!-- Note: Google type doesn't support some of these. -->
+            <!-- Note: Google type doesn't have obsolete ones -->
             <Type type="home" />
             <Type type="work" />
             <Type type="other" />
@@ -248,17 +244,13 @@
 
         <!--
             Event
-
-            The parser should be able to handle it, but not tested.
         -->
-        <!-- Google definition.
         <DataKind kind="event" dateWithTime="false">
             <Type type="birthday" maxOccurs="1" yearOptional="true" />
             <Type type="anniversary" />
             <Type type="other" />
             <Type type="custom" />
         </DataKind>
-        -->
 
         <!--
             Exchange definition.  dateWithTime is needed only for Exchange.
@@ -268,11 +260,9 @@
         -->
 
         <!--
-            Relationship.
-
-            The parser should be able to handle it, but not tested.
-
-        <DataKind kind="relation" >
+            Relationship
+        -->
+        <DataKind kind="relationship" >
             <Type type="assistant" />
             <Type type="brother" />
             <Type type="child" />
@@ -289,8 +279,5 @@
             <Type type="spouse" />
             <Type type="custom" />
         </DataKind>
-        -->
-
     </EditSchema>
-
 </ContactsAccountType>
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 4db73b3..6872604 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -20,8 +20,6 @@
 import static android.content.ContentProviderOperation.TYPE_INSERT;
 import static android.content.ContentProviderOperation.TYPE_UPDATE;
 
-import com.google.android.collect.Lists;
-
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountType.EditType;
 import com.android.contacts.model.AccountTypeManager;
@@ -35,10 +33,10 @@
 import com.android.contacts.tests.mocks.ContactsMockContext;
 import com.android.contacts.tests.mocks.MockAccountTypeManager;
 import com.android.contacts.tests.mocks.MockContentProvider;
+import com.google.android.collect.Lists;
 
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Entity;
 import android.net.Uri;
 import android.os.Bundle;
@@ -87,56 +85,60 @@
     public static class MockContactsSource extends AccountType {
 
         MockContactsSource() {
-            this.accountType = TEST_ACCOUNT_TYPE;
+            try {
+                this.accountType = TEST_ACCOUNT_TYPE;
 
-            final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                    R.string.nameLabelsGroup, -1, true, -1);
-            nameKind.typeOverallMax = 1;
-            addKind(nameKind);
+                final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
+                        R.string.nameLabelsGroup, -1, true, -1);
+                nameKind.typeOverallMax = 1;
+                addKind(nameKind);
 
-            // Phone allows maximum 2 home, 1 work, and unlimited other, with
-            // constraint of 5 numbers maximum.
-            final DataKind phoneKind = new DataKind(
-                    Phone.CONTENT_ITEM_TYPE, -1, 10, true, -1);
+                // Phone allows maximum 2 home, 1 work, and unlimited other, with
+                // constraint of 5 numbers maximum.
+                final DataKind phoneKind = new DataKind(
+                        Phone.CONTENT_ITEM_TYPE, -1, 10, true, -1);
 
-            phoneKind.typeOverallMax = 5;
-            phoneKind.typeColumn = Phone.TYPE;
-            phoneKind.typeList = Lists.newArrayList();
-            phoneKind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2));
-            phoneKind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1));
-            phoneKind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true));
-            phoneKind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));
+                phoneKind.typeOverallMax = 5;
+                phoneKind.typeColumn = Phone.TYPE;
+                phoneKind.typeList = Lists.newArrayList();
+                phoneKind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2));
+                phoneKind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1));
+                phoneKind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true));
+                phoneKind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));
 
-            phoneKind.fieldList = Lists.newArrayList();
-            phoneKind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
-            phoneKind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
+                phoneKind.fieldList = Lists.newArrayList();
+                phoneKind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
+                phoneKind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
 
-            addKind(phoneKind);
+                addKind(phoneKind);
 
-            // Email is unlimited
-            final DataKind emailKind = new DataKind(
-                    Email.CONTENT_ITEM_TYPE, -1, 10, true, -1);
-            emailKind.typeOverallMax = -1;
-            emailKind.fieldList = Lists.newArrayList();
-            emailKind.fieldList.add(new EditField(Email.DATA, -1, -1));
-            addKind(emailKind);
+                // Email is unlimited
+                final DataKind emailKind = new DataKind(
+                        Email.CONTENT_ITEM_TYPE, -1, 10, true, -1);
+                emailKind.typeOverallMax = -1;
+                emailKind.fieldList = Lists.newArrayList();
+                emailKind.fieldList.add(new EditField(Email.DATA, -1, -1));
+                addKind(emailKind);
 
-            // IM is only one
-            final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10,
-                    true, -1);
-            imKind.typeOverallMax = 1;
-            imKind.fieldList = Lists.newArrayList();
-            imKind.fieldList.add(new EditField(Im.DATA, -1, -1));
-            addKind(imKind);
+                // IM is only one
+                final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10,
+                        true, -1);
+                imKind.typeOverallMax = 1;
+                imKind.fieldList = Lists.newArrayList();
+                imKind.fieldList.add(new EditField(Im.DATA, -1, -1));
+                addKind(imKind);
 
-            // Organization is only one
-            final DataKind orgKind = new DataKind(
-                    Organization.CONTENT_ITEM_TYPE, -1, 10, true, -1);
-            orgKind.typeOverallMax = 1;
-            orgKind.fieldList = Lists.newArrayList();
-            orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
-            orgKind.fieldList.add(new EditField(Organization.TITLE, -1, -1));
-            addKind(orgKind);
+                // Organization is only one
+                final DataKind orgKind = new DataKind(
+                        Organization.CONTENT_ITEM_TYPE, -1, 10, true, -1);
+                orgKind.typeOverallMax = 1;
+                orgKind.fieldList = Lists.newArrayList();
+                orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
+                orgKind.fieldList.add(new EditField(Organization.TITLE, -1, -1));
+                addKind(orgKind);
+            } catch (DefinitionException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         @Override
diff --git a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
index 15a1320..ba3d0eb 100644
--- a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
@@ -19,12 +19,15 @@
 import com.android.contacts.tests.R;
 
 import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.XmlResourceParser;
 import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Note;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
@@ -32,6 +35,10 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.List;
+
+import libcore.util.Objects;
+
 /**
  * Test case for {@link ExternalAccountType}.
  *
@@ -40,7 +47,6 @@
  */
 @SmallTest
 public class ExternalAccountTypeTest extends AndroidTestCase {
-
     public void testResolveExternalResId() {
         final Context c = getContext();
         // In this test we use the test package itself as an external package.
@@ -63,12 +69,36 @@
                 "@string/test_string", packageName, ""));
     }
 
+    /**
+     * Initialize with an invalid package name and see if type type will *not* be initialized.
+     */
+    public void testNoPackage() {
+        final ExternalAccountType type = new ExternalAccountType(getContext(),
+                "!!!no such package name!!!", false);
+        assertFalse(type.isInitialized());
+    }
+
+    /**
+     * Initialize with the name of an existing package, which has no contacts.xml metadata.
+     */
+    public void testNoMetadata() {
+        // Use the main application package, which does exist, but has no contacts.xml in it.
+        String packageName = getContext().getPackageName();
+        final ExternalAccountType type = new ExternalAccountType(getContext(),
+                packageName, false);
+        assertTrue(type.isInitialized());
+    }
+
+    /**
+     * Initialize with the test package itself and see if EditSchema is correctly parsed.
+     */
     public void testEditSchema() {
         final ExternalAccountType type = new ExternalAccountType(getContext(),
                 getTestContext().getPackageName(), false);
 
         assertTrue(type.isInitialized());
 
+        // Let's just check if the DataKinds are registered.
         assertNotNull(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE));
         assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME));
         assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME));
@@ -80,8 +110,69 @@
         assertNotNull(type.getKindForMimetype(Note.CONTENT_ITEM_TYPE));
         assertNotNull(type.getKindForMimetype(Website.CONTENT_ITEM_TYPE));
         assertNotNull(type.getKindForMimetype(SipAddress.CONTENT_ITEM_TYPE));
-        assertNotNull(type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE));
+        assertNotNull(type.getKindForMimetype(Event.CONTENT_ITEM_TYPE));
+        assertNotNull(type.getKindForMimetype(Relation.CONTENT_ITEM_TYPE));
+    }
 
-        // TODO Write more extensive check -- compare to FallbackAccountType?
+    /**
+     * Initialize with "contacts_fallback.xml" and compare the DataKinds to those of
+     * {@link FallbackAccountType}.
+     */
+    public void testEditSchema_fallback() {
+        final ExternalAccountType type = new ExternalAccountType(getContext(),
+                getTestContext().getPackageName(), false,
+                getTestContext().getResources().getXml(R.xml.contacts_fallback)
+                );
+
+        assertTrue(type.isInitialized());
+
+        // Create a fallback type with the same resource package name, and compare all the data
+        // kinds to its.
+        final AccountType reference = FallbackAccountType.createForTest(
+                getContext(), type.resPackageName);
+
+        assertsDataKindEquals(reference.getSortedDataKinds(), type.getSortedDataKinds());
+    }
+
+    private static void assertsDataKindEquals(List<DataKind> expectedKinds,
+            List<DataKind> actualKinds) {
+        final int count = Math.max(actualKinds.size(), expectedKinds.size());
+        for (int i = 0; i < count; i++) {
+            String actual =  actualKinds.size() > i ? actualKinds.get(i).toString() : "(n/a)";
+            String expected =  expectedKinds.size() > i ? expectedKinds.get(i).toString() : "(n/a)";
+
+            // Because assertEquals()'s output is not very friendly when comparing two similar
+            // strings, we manually do the check.
+            if (!Objects.equal(actual, expected)) {
+                final int commonPrefixEnd = findCommonPrefixEnd(actual, expected);
+                fail("Kind #" + i
+                        + "\n[Actual]\n" + insertMarkerAt(actual, commonPrefixEnd)
+                        + "\n[Expected]\n" + insertMarkerAt(expected, commonPrefixEnd));
+            }
+        }
+    }
+
+    private static int findCommonPrefixEnd(String s1, String s2) {
+        int i = 0;
+        for (;;) {
+            final boolean s1End = (s1.length() <= i);
+            final boolean s2End = (s2.length() <= i);
+            if (s1End || s2End) {
+                return i;
+            }
+            if (s1.charAt(i) != s2.charAt(i)) {
+                return i;
+            }
+            i++;
+        }
+    }
+
+    private static String insertMarkerAt(String s, int position) {
+        final String MARKER = "***";
+        if (position > s.length()) {
+            return s + MARKER;
+        } else {
+            return new StringBuilder(s).insert(position, MARKER).toString();
+        }
     }
 }