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/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();
+ }
}
}