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