1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.common.model.account;
18
19import android.content.Context;
20import android.provider.ContactsContract.CommonDataKinds.Email;
21import android.provider.ContactsContract.CommonDataKinds.Event;
22import android.provider.ContactsContract.CommonDataKinds.Im;
23import android.provider.ContactsContract.CommonDataKinds.Note;
24import android.provider.ContactsContract.CommonDataKinds.Organization;
25import android.provider.ContactsContract.CommonDataKinds.Photo;
26import android.provider.ContactsContract.CommonDataKinds.Relation;
27import android.provider.ContactsContract.CommonDataKinds.SipAddress;
28import android.provider.ContactsContract.CommonDataKinds.StructuredName;
29import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
30import android.provider.ContactsContract.CommonDataKinds.Website;
31import android.test.AndroidTestCase;
32import android.test.suitebuilder.annotation.SmallTest;
33import android.util.Log;
34
35import com.android.contacts.common.model.dataitem.DataKind;
36import com.android.contacts.common.unittest.R;
37import com.google.common.base.Objects;
38
39import java.util.List;
40
41/**
42 * Test case for {@link com.android.contacts.common.model.account.ExternalAccountType}.
43 *
44 * adb shell am instrument -w -e class com.android.contacts.model.ExternalAccountTypeTest \
45       com.android.contacts.tests/android.test.InstrumentationTestRunner
46 */
47@SmallTest
48public class ExternalAccountTypeTest extends AndroidTestCase {
49    public void testResolveExternalResId() {
50        final Context c = getContext();
51        // In this test we use the test package itself as an external package.
52        final String packageName = getTestContext().getPackageName();
53
54        // Resource name empty.
55        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, null, packageName, ""));
56        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "", packageName, ""));
57
58        // Name doesn't begin with '@'
59        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "x", packageName, ""));
60
61        // Invalid resource name
62        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@", packageName, ""));
63        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@a", packageName, ""));
64        assertEquals(-1, ExternalAccountType.resolveExternalResId(c, "@a/b", packageName, ""));
65
66        // Valid resource name
67        assertEquals(R.string.test_string, ExternalAccountType.resolveExternalResId(c,
68                "@string/test_string", packageName, ""));
69    }
70
71    /**
72     * Initialize with an invalid package name and see if type will be initialized, but empty.
73     */
74    public void testNoPackage() {
75        final ExternalAccountType type = new ExternalAccountType(getContext(),
76                "!!!no such package name!!!", false);
77        assertTrue(type.isInitialized());
78    }
79
80    /**
81     * Initialize with the test package itself and see if EditSchema is correctly parsed.
82     */
83    public void testEditSchema() {
84        final ExternalAccountType type = new ExternalAccountType(getContext(),
85                getTestContext().getPackageName(), false);
86
87        assertTrue(type.isInitialized());
88
89        // Let's just check if the DataKinds are registered.
90        assertNotNull(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE));
91        assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME));
92        assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME));
93        assertNotNull(type.getKindForMimetype(Email.CONTENT_ITEM_TYPE));
94        assertNotNull(type.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE));
95        assertNotNull(type.getKindForMimetype(Im.CONTENT_ITEM_TYPE));
96        assertNotNull(type.getKindForMimetype(Organization.CONTENT_ITEM_TYPE));
97        assertNotNull(type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE));
98        assertNotNull(type.getKindForMimetype(Note.CONTENT_ITEM_TYPE));
99        assertNotNull(type.getKindForMimetype(Website.CONTENT_ITEM_TYPE));
100        assertNotNull(type.getKindForMimetype(SipAddress.CONTENT_ITEM_TYPE));
101        assertNotNull(type.getKindForMimetype(Event.CONTENT_ITEM_TYPE));
102        assertNotNull(type.getKindForMimetype(Relation.CONTENT_ITEM_TYPE));
103    }
104
105    /**
106     * Initialize with "contacts_fallback.xml" and compare the DataKinds to those of
107     * {@link com.android.contacts.common.model.account.FallbackAccountType}.
108     */
109    public void testEditSchema_fallback() {
110        final ExternalAccountType type = new ExternalAccountType(getContext(),
111                getTestContext().getPackageName(), false,
112                getTestContext().getResources().getXml(R.xml.contacts_fallback)
113                );
114
115        assertTrue(type.isInitialized());
116
117        // Create a fallback type with the same resource package name, and compare all the data
118        // kinds to its.
119        final AccountType reference = FallbackAccountType.createWithPackageNameForTest(
120                getContext(), type.resourcePackageName);
121
122        assertsDataKindEquals(reference.getSortedDataKinds(), type.getSortedDataKinds());
123    }
124
125    public void testEditSchema_mustHaveChecks() {
126        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_base, true);
127        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_photo, false);
128        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name, false);
129        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr1, false);
130        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr2, false);
131        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr3, false);
132        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr4, false);
133        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr5, false);
134        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr6, false);
135        checkEditSchema_mustHaveChecks(R.xml.missing_contacts_name_attr7, false);
136    }
137
138    private void checkEditSchema_mustHaveChecks(int xmlResId, boolean expectInitialized) {
139        final ExternalAccountType type = new ExternalAccountType(getContext(),
140                getTestContext().getPackageName(), false,
141                getTestContext().getResources().getXml(xmlResId)
142                );
143
144        assertEquals(expectInitialized, type.isInitialized());
145    }
146
147    /**
148     * Initialize with "contacts_readonly.xml" and see if all data kinds are correctly registered.
149     */
150    public void testReadOnlyDefinition() {
151        final ExternalAccountType type = new ExternalAccountType(getContext(),
152                getTestContext().getPackageName(), false,
153                getTestContext().getResources().getXml(R.xml.contacts_readonly)
154                );
155        assertTrue(type.isInitialized());
156
157        // Shouldn't have a "null" mimetype.
158        assertTrue(type.getKindForMimetype(null) == null);
159
160        // 3 kinds are defined in XML and 4 are added by default.
161        assertEquals(4 + 3, type.getSortedDataKinds().size());
162
163        // Check for the default kinds.
164        assertNotNull(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE));
165        assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME));
166        assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME));
167        assertNotNull(type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE));
168
169        // Check for type specific kinds.
170        DataKind kind = type.getKindForMimetype("vnd.android.cursor.item/a.b.c");
171        assertNotNull(kind);
172        // No check for icon -- we actually just ignore it.
173        assertEquals("data1", ((BaseAccountType.SimpleInflater) kind.actionHeader)
174                .getColumnNameForTest());
175        assertEquals("data2", ((BaseAccountType.SimpleInflater) kind.actionBody)
176                .getColumnNameForTest());
177
178        kind = type.getKindForMimetype("vnd.android.cursor.item/d.e.f");
179        assertNotNull(kind);
180        assertEquals("data3", ((BaseAccountType.SimpleInflater) kind.actionHeader)
181                .getColumnNameForTest());
182        assertEquals("data4", ((BaseAccountType.SimpleInflater) kind.actionBody)
183                .getColumnNameForTest());
184
185        kind = type.getKindForMimetype("vnd.android.cursor.item/xyz");
186        assertNotNull(kind);
187        assertEquals("data5", ((BaseAccountType.SimpleInflater) kind.actionHeader)
188                .getColumnNameForTest());
189        assertEquals("data6", ((BaseAccountType.SimpleInflater) kind.actionBody)
190                .getColumnNameForTest());
191    }
192
193    private static void assertsDataKindEquals(List<DataKind> expectedKinds,
194            List<DataKind> actualKinds) {
195        final int count = Math.max(actualKinds.size(), expectedKinds.size());
196        for (int i = 0; i < count; i++) {
197            String actual =  actualKinds.size() > i ? actualKinds.get(i).toString() : "(n/a)";
198            String expected =  expectedKinds.size() > i ? expectedKinds.get(i).toString() : "(n/a)";
199
200            // Because assertEquals()'s output is not very friendly when comparing two similar
201            // strings, we manually do the check.
202            if (!Objects.equal(actual, expected)) {
203                final int commonPrefixEnd = findCommonPrefixEnd(actual, expected);
204                fail("Kind #" + i
205                        + "\n[Actual]\n" + insertMarkerAt(actual, commonPrefixEnd)
206                        + "\n[Expected]\n" + insertMarkerAt(expected, commonPrefixEnd));
207            }
208        }
209    }
210
211    private static int findCommonPrefixEnd(String s1, String s2) {
212        int i = 0;
213        for (;;) {
214            final boolean s1End = (s1.length() <= i);
215            final boolean s2End = (s2.length() <= i);
216            if (s1End || s2End) {
217                return i;
218            }
219            if (s1.charAt(i) != s2.charAt(i)) {
220                return i;
221            }
222            i++;
223        }
224    }
225
226    private static String insertMarkerAt(String s, int position) {
227        final String MARKER = "***";
228        if (position > s.length()) {
229            return s + MARKER;
230        } else {
231            return new StringBuilder(s).insert(position, MARKER).toString();
232        }
233    }
234}
235