1/* 2 * Copyright (C) 2010 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 */ 16package android.pim.vcard.test_utils; 17 18import android.content.ContentProviderOperation; 19import android.content.ContentProviderResult; 20import android.content.ContentValues; 21import android.net.Uri; 22import android.provider.ContactsContract.CommonDataKinds.Email; 23import android.provider.ContactsContract.CommonDataKinds.Event; 24import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 25import android.provider.ContactsContract.CommonDataKinds.Im; 26import android.provider.ContactsContract.CommonDataKinds.Nickname; 27import android.provider.ContactsContract.CommonDataKinds.Note; 28import android.provider.ContactsContract.CommonDataKinds.Organization; 29import android.provider.ContactsContract.CommonDataKinds.Phone; 30import android.provider.ContactsContract.CommonDataKinds.Photo; 31import android.provider.ContactsContract.CommonDataKinds.Relation; 32import android.provider.ContactsContract.CommonDataKinds.StructuredName; 33import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 34import android.provider.ContactsContract.CommonDataKinds.Website; 35import android.provider.ContactsContract.Data; 36import android.provider.ContactsContract.RawContacts; 37import android.test.AndroidTestCase; 38import android.test.mock.MockContentProvider; 39import android.text.TextUtils; 40 41import junit.framework.TestCase; 42 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.Collection; 46import java.util.HashMap; 47import java.util.HashSet; 48import java.util.Map; 49import java.util.Map.Entry; 50import java.util.Set; 51import java.util.SortedMap; 52import java.util.TreeMap; 53 54public class ImportTestProvider extends MockContentProvider { 55 private static final Set<String> sKnownMimeTypeSet = 56 new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, 57 Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, 58 Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE, 59 Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE, 60 Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE, 61 Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE, 62 Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE, 63 GroupMembership.CONTENT_ITEM_TYPE)); 64 65 final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; 66 67 public ImportTestProvider(AndroidTestCase androidTestCase) { 68 mMimeTypeToExpectedContentValues = 69 new HashMap<String, Collection<ContentValues>>(); 70 for (String acceptanbleMimeType : sKnownMimeTypeSet) { 71 // Do not use HashSet since the current implementation changes the content of 72 // ContentValues after the insertion, which make the result of hashCode() 73 // changes... 74 mMimeTypeToExpectedContentValues.put( 75 acceptanbleMimeType, new ArrayList<ContentValues>()); 76 } 77 } 78 79 public void addExpectedContentValues(ContentValues expectedContentValues) { 80 final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE); 81 if (!sKnownMimeTypeSet.contains(mimeType)) { 82 TestCase.fail(String.format( 83 "Unknow MimeType %s in the test code. Test code should be broken.", 84 mimeType)); 85 } 86 87 final Collection<ContentValues> contentValuesCollection = 88 mMimeTypeToExpectedContentValues.get(mimeType); 89 contentValuesCollection.add(expectedContentValues); 90 } 91 92 @Override 93 public ContentProviderResult[] applyBatch( 94 ArrayList<ContentProviderOperation> operations) { 95 if (operations == null) { 96 TestCase.fail("There is no operation."); 97 } 98 99 final int size = operations.size(); 100 ContentProviderResult[] fakeResultArray = new ContentProviderResult[size]; 101 for (int i = 0; i < size; i++) { 102 Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i)); 103 fakeResultArray[i] = new ContentProviderResult(uri); 104 } 105 106 for (int i = 0; i < size; i++) { 107 ContentProviderOperation operation = operations.get(i); 108 ContentValues contentValues = operation.resolveValueBackReferences( 109 fakeResultArray, i); 110 } 111 for (int i = 0; i < size; i++) { 112 ContentProviderOperation operation = operations.get(i); 113 ContentValues actualContentValues = operation.resolveValueBackReferences( 114 fakeResultArray, i); 115 final Uri uri = operation.getUri(); 116 if (uri.equals(RawContacts.CONTENT_URI)) { 117 TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); 118 TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); 119 } else if (uri.equals(Data.CONTENT_URI)) { 120 final String mimeType = actualContentValues.getAsString(Data.MIMETYPE); 121 if (!sKnownMimeTypeSet.contains(mimeType)) { 122 TestCase.fail(String.format( 123 "Unknown MimeType %s. Probably added after developing this test", 124 mimeType)); 125 } 126 // Remove data meaningless in this unit tests. 127 // Specifically, Data.DATA1 - DATA7 are set to null or empty String 128 // regardless of the input, but it may change depending on how 129 // resolver-related code handles it. 130 // Here, we ignore these implementation-dependent specs and 131 // just check whether vCard importer correctly inserts rellevent data. 132 Set<String> keyToBeRemoved = new HashSet<String>(); 133 for (Entry<String, Object> entry : actualContentValues.valueSet()) { 134 Object value = entry.getValue(); 135 if (value == null || TextUtils.isEmpty(value.toString())) { 136 keyToBeRemoved.add(entry.getKey()); 137 } 138 } 139 for (String key: keyToBeRemoved) { 140 actualContentValues.remove(key); 141 } 142 /* for testing 143 Log.d("@@@", 144 String.format("MimeType: %s, data: %s", 145 mimeType, actualContentValues.toString())); */ 146 // Remove RAW_CONTACT_ID entry just for safety, since we do not care 147 // how resolver-related code handles the entry in this unit test, 148 if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) { 149 actualContentValues.remove(Data.RAW_CONTACT_ID); 150 } 151 final Collection<ContentValues> contentValuesCollection = 152 mMimeTypeToExpectedContentValues.get(mimeType); 153 if (contentValuesCollection.isEmpty()) { 154 TestCase.fail("ContentValues for MimeType " + mimeType 155 + " is not expected at all (" + actualContentValues + ")"); 156 } 157 boolean checked = false; 158 for (ContentValues expectedContentValues : contentValuesCollection) { 159 /*for testing 160 Log.d("@@@", "expected: " 161 + convertToEasilyReadableString(expectedContentValues)); 162 Log.d("@@@", "actual : " 163 + convertToEasilyReadableString(actualContentValues));*/ 164 if (equalsForContentValues(expectedContentValues, 165 actualContentValues)) { 166 TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); 167 checked = true; 168 break; 169 } 170 } 171 if (!checked) { 172 final StringBuilder builder = new StringBuilder(); 173 builder.append("\n"); 174 builder.append("Unexpected: "); 175 builder.append(convertToEasilyReadableString(actualContentValues)); 176 builder.append("\n"); 177 builder.append("Expected : "); 178 for (ContentValues expectedContentValues : contentValuesCollection) { 179 builder.append(convertToEasilyReadableString(expectedContentValues)); 180 } 181 TestCase.fail(builder.toString()); 182 } 183 } else { 184 TestCase.fail("Unexpected Uri has come: " + uri); 185 } 186 } // for (int i = 0; i < size; i++) { 187 return fakeResultArray; 188 } 189 190 public void verify() { 191 StringBuilder builder = new StringBuilder(); 192 for (Collection<ContentValues> contentValuesCollection : 193 mMimeTypeToExpectedContentValues.values()) { 194 for (ContentValues expectedContentValues: contentValuesCollection) { 195 builder.append(convertToEasilyReadableString(expectedContentValues)); 196 builder.append("\n"); 197 } 198 } 199 if (builder.length() > 0) { 200 final String failMsg = 201 "There is(are) remaining expected ContentValues instance(s): \n" 202 + builder.toString(); 203 TestCase.fail(failMsg); 204 } 205 } 206 207 /** 208 * Utility method to print ContentValues whose content is printed with sorted keys. 209 */ 210 private String convertToEasilyReadableString(ContentValues contentValues) { 211 if (contentValues == null) { 212 return "null"; 213 } 214 String mimeTypeValue = ""; 215 SortedMap<String, String> sortedMap = new TreeMap<String, String>(); 216 for (Entry<String, Object> entry : contentValues.valueSet()) { 217 final String key = entry.getKey(); 218 final Object value = entry.getValue(); 219 final String valueString = (value != null ? value.toString() : null); 220 if (Data.MIMETYPE.equals(key)) { 221 mimeTypeValue = valueString; 222 } else { 223 TestCase.assertNotNull(key); 224 sortedMap.put(key, valueString); 225 } 226 } 227 StringBuilder builder = new StringBuilder(); 228 builder.append(Data.MIMETYPE); 229 builder.append('='); 230 builder.append(mimeTypeValue); 231 for (Entry<String, String> entry : sortedMap.entrySet()) { 232 final String key = entry.getKey(); 233 final String value = entry.getValue(); 234 builder.append(' '); 235 builder.append(key); 236 builder.append("=\""); 237 builder.append(value); 238 builder.append('"'); 239 } 240 return builder.toString(); 241 } 242 243 private static boolean equalsForContentValues( 244 final ContentValues expected, final ContentValues actual) { 245 if (expected == actual) { 246 return true; 247 } else if (expected == null || actual == null || expected.size() != actual.size()) { 248 return false; 249 } 250 251 for (Entry<String, Object> entry : expected.valueSet()) { 252 final String key = entry.getKey(); 253 final Object value = entry.getValue(); 254 if (!actual.containsKey(key)) { 255 return false; 256 } 257 // Type mismatch usuall happens as importer doesn't care the type of each value. 258 // For example, variable type might be Integer when importing the type of TEL, 259 // while variable type would be String when importing the type of RELATION. 260 final Object actualValue = actual.get(key); 261 if (value instanceof byte[]) { 262 if (!Arrays.equals((byte[])value, (byte[])actualValue)) { 263 byte[] e = (byte[])value; 264 byte[] a = (byte[])actualValue; 265 return false; 266 } 267 } else if (!value.equals(actualValue) && 268 !value.toString().equals(actualValue.toString())) { 269 return false; 270 } 271 } 272 return true; 273 } 274}