1/* 2 * Copyright (C) 2009 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; 18 19import android.content.ContentProviderOperation; 20import android.content.ContentValues; 21import android.content.Context; 22import android.os.Build; 23import android.os.Parcel; 24import android.provider.ContactsContract.CommonDataKinds.Phone; 25import android.provider.ContactsContract.Data; 26import android.provider.ContactsContract.RawContacts; 27import android.test.AndroidTestCase; 28import android.test.suitebuilder.annotation.LargeTest; 29 30import com.android.contacts.common.compat.CompatUtils; 31import com.android.contacts.common.model.BuilderWrapper; 32import com.android.contacts.common.model.CPOWrapper; 33import com.android.contacts.common.model.RawContact; 34import com.android.contacts.common.model.RawContactDelta; 35import com.android.contacts.common.model.ValuesDelta; 36import com.google.common.collect.Lists; 37 38import java.util.ArrayList; 39 40/** 41 * Tests for {@link RawContactDelta} and {@link ValuesDelta}. These tests 42 * focus on passing changes across {@link Parcel}, and verifying that they 43 * correctly build expected "diff" operations. 44 */ 45@LargeTest 46public class RawContactDeltaTests extends AndroidTestCase { 47 public static final String TAG = "EntityDeltaTests"; 48 49 public static final long TEST_CONTACT_ID = 12; 50 public static final long TEST_PHONE_ID = 24; 51 52 public static final String TEST_PHONE_NUMBER_1 = "218-555-1111"; 53 public static final String TEST_PHONE_NUMBER_2 = "218-555-2222"; 54 55 public static final String TEST_ACCOUNT_NAME = "TEST"; 56 57 public RawContactDeltaTests() { 58 super(); 59 } 60 61 @Override 62 public void setUp() { 63 mContext = getContext(); 64 } 65 66 public static RawContact getRawContact(Context context, long contactId, long phoneId) { 67 // Build an existing contact read from database 68 final ContentValues contact = new ContentValues(); 69 contact.put(RawContacts.VERSION, 43); 70 contact.put(RawContacts._ID, contactId); 71 72 final ContentValues phone = new ContentValues(); 73 phone.put(Data._ID, phoneId); 74 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 75 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 76 phone.put(Phone.TYPE, Phone.TYPE_HOME); 77 78 final RawContact before = new RawContact(contact); 79 before.addDataItemValues(phone); 80 return before; 81 } 82 83 /** 84 * Test that {@link RawContactDelta#mergeAfter(RawContactDelta)} correctly passes 85 * any changes through the {@link Parcel} object. This enforces that 86 * {@link RawContactDelta} should be identical when serialized against the same 87 * "before" {@link RawContact}. 88 */ 89 public void testParcelChangesNone() { 90 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 91 final RawContactDelta source = RawContactDelta.fromBefore(before); 92 final RawContactDelta dest = RawContactDelta.fromBefore(before); 93 94 // Merge modified values and assert they match 95 final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); 96 assertEquals("Unexpected change when merging", source, merged); 97 } 98 99 public void testParcelChangesInsert() { 100 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 101 final RawContactDelta source = RawContactDelta.fromBefore(before); 102 final RawContactDelta dest = RawContactDelta.fromBefore(before); 103 104 // Add a new row and pass across parcel, should be same 105 final ContentValues phone = new ContentValues(); 106 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 107 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 108 phone.put(Phone.TYPE, Phone.TYPE_WORK); 109 source.addEntry(ValuesDelta.fromAfter(phone)); 110 111 // Merge modified values and assert they match 112 final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); 113 assertEquals("Unexpected change when merging", source, merged); 114 } 115 116 public void testParcelChangesUpdate() { 117 // Update existing row and pass across parcel, should be same 118 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 119 final RawContactDelta source = RawContactDelta.fromBefore(before); 120 final RawContactDelta dest = RawContactDelta.fromBefore(before); 121 122 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 123 child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 124 125 // Merge modified values and assert they match 126 final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); 127 assertEquals("Unexpected change when merging", source, merged); 128 } 129 130 public void testParcelChangesDelete() { 131 // Delete a row and pass across parcel, should be same 132 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 133 final RawContactDelta source = RawContactDelta.fromBefore(before); 134 final RawContactDelta dest = RawContactDelta.fromBefore(before); 135 136 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 137 child.markDeleted(); 138 139 // Merge modified values and assert they match 140 final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); 141 assertEquals("Unexpected change when merging", source, merged); 142 } 143 144 public void testValuesDiffDelete() { 145 final ContentValues before = new ContentValues(); 146 before.put(Data._ID, TEST_PHONE_ID); 147 before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); 148 149 final ValuesDelta values = ValuesDelta.fromBefore(before); 150 values.markDeleted(); 151 152 // Should produce a delete action 153 final BuilderWrapper builderWrapper = values.buildDiffWrapper(Data.CONTENT_URI); 154 final boolean isDelete = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M 155 ? builderWrapper.getBuilder().build().isDelete() 156 : builderWrapper.getType() == CompatUtils.TYPE_DELETE; 157 assertTrue("Didn't produce delete action", isDelete); 158 } 159 160 /** 161 * Test that {@link RawContactDelta#buildDiffWrapper(ArrayList)} is correctly built for 162 * insert, update, and delete cases. This only tests a subset of possible 163 * {@link Data} row changes. 164 */ 165 public void testEntityDiffNone() { 166 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 167 final RawContactDelta source = RawContactDelta.fromBefore(before); 168 169 // Assert that writing unchanged produces few operations 170 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 171 source.buildDiffWrapper(diff); 172 173 assertTrue("Created changes when none needed", (diff.size() == 0)); 174 } 175 176 public void testEntityDiffNoneInsert() { 177 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 178 final RawContactDelta source = RawContactDelta.fromBefore(before); 179 180 // Insert a new phone number 181 final ContentValues phone = new ContentValues(); 182 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 183 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 184 phone.put(Phone.TYPE, Phone.TYPE_WORK); 185 source.addEntry(ValuesDelta.fromAfter(phone)); 186 187 // Assert two operations: insert Data row and enforce version 188 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 189 source.buildAssertWrapper(diff); 190 source.buildDiffWrapper(diff); 191 assertEquals("Unexpected operations", 4, diff.size()); 192 { 193 final CPOWrapper cpoWrapper = diff.get(0); 194 assertTrue("Expected version enforcement", CompatUtils.isAssertQueryCompat(cpoWrapper)); 195 } 196 { 197 final CPOWrapper cpoWrapper = diff.get(1); 198 final ContentProviderOperation oper = cpoWrapper.getOperation(); 199 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 200 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 201 } 202 { 203 final CPOWrapper cpoWrapper = diff.get(2); 204 final ContentProviderOperation oper = cpoWrapper.getOperation(); 205 assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper)); 206 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 207 } 208 { 209 final CPOWrapper cpoWrapper = diff.get(3); 210 final ContentProviderOperation oper = cpoWrapper.getOperation(); 211 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 212 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 213 } 214 } 215 216 public void testEntityDiffUpdateInsert() { 217 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 218 final RawContactDelta source = RawContactDelta.fromBefore(before); 219 220 // Update parent contact values 221 source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED); 222 223 // Insert a new phone number 224 final ContentValues phone = new ContentValues(); 225 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 226 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 227 phone.put(Phone.TYPE, Phone.TYPE_WORK); 228 source.addEntry(ValuesDelta.fromAfter(phone)); 229 230 // Assert three operations: update Contact, insert Data row, enforce version 231 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 232 source.buildAssertWrapper(diff); 233 source.buildDiffWrapper(diff); 234 assertEquals("Unexpected operations", 5, diff.size()); 235 { 236 final CPOWrapper cpoWrapper = diff.get(0); 237 assertTrue("Expected version enforcement", CompatUtils.isAssertQueryCompat(cpoWrapper)); 238 } 239 { 240 final CPOWrapper cpoWrapper = diff.get(1); 241 final ContentProviderOperation oper = cpoWrapper.getOperation(); 242 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 243 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 244 } 245 { 246 final CPOWrapper cpoWrapper = diff.get(2); 247 final ContentProviderOperation oper = cpoWrapper.getOperation(); 248 assertTrue("Incorrect type", CompatUtils.isUpdateCompat(cpoWrapper)); 249 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 250 } 251 { 252 final CPOWrapper cpoWrapper = diff.get(3); 253 final ContentProviderOperation oper = cpoWrapper.getOperation(); 254 assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper)); 255 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 256 } 257 { 258 final CPOWrapper cpoWrapper = diff.get(4); 259 final ContentProviderOperation oper = cpoWrapper.getOperation(); 260 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 261 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 262 } 263 } 264 265 public void testEntityDiffNoneUpdate() { 266 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 267 final RawContactDelta source = RawContactDelta.fromBefore(before); 268 269 // Update existing phone number 270 final ValuesDelta child = source.getEntry(TEST_PHONE_ID); 271 child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 272 273 // Assert that version is enforced 274 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 275 source.buildAssertWrapper(diff); 276 source.buildDiffWrapper(diff); 277 assertEquals("Unexpected operations", 4, diff.size()); 278 { 279 final CPOWrapper cpoWrapper = diff.get(0); 280 assertTrue("Expected version enforcement", CompatUtils.isAssertQueryCompat(cpoWrapper)); 281 } 282 { 283 final CPOWrapper cpoWrapper = diff.get(1); 284 final ContentProviderOperation oper = cpoWrapper.getOperation(); 285 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 286 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 287 } 288 { 289 final CPOWrapper cpoWrapper = diff.get(2); 290 final ContentProviderOperation oper = cpoWrapper.getOperation(); 291 assertTrue("Incorrect type", CompatUtils.isUpdateCompat(cpoWrapper)); 292 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 293 } 294 { 295 final CPOWrapper cpoWrapper = diff.get(3); 296 final ContentProviderOperation oper = cpoWrapper.getOperation(); 297 assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper)); 298 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 299 } 300 } 301 302 public void testEntityDiffDelete() { 303 final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); 304 final RawContactDelta source = RawContactDelta.fromBefore(before); 305 306 // Delete entire entity 307 source.getValues().markDeleted(); 308 309 // Assert two operations: delete Contact and enforce version 310 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 311 source.buildAssertWrapper(diff); 312 source.buildDiffWrapper(diff); 313 assertEquals("Unexpected operations", 2, diff.size()); 314 { 315 final CPOWrapper cpoWrapper = diff.get(0); 316 assertTrue("Expected version enforcement", CompatUtils.isAssertQueryCompat(cpoWrapper)); 317 } 318 { 319 final CPOWrapper cpoWrapper = diff.get(1); 320 final ContentProviderOperation oper = cpoWrapper.getOperation(); 321 assertTrue("Incorrect type", CompatUtils.isDeleteCompat(cpoWrapper)); 322 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 323 } 324 } 325 326 public void testEntityDiffInsert() { 327 // Insert a RawContact 328 final ContentValues after = new ContentValues(); 329 after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); 330 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 331 332 final ValuesDelta values = ValuesDelta.fromAfter(after); 333 final RawContactDelta source = new RawContactDelta(values); 334 335 // Assert two operations: insert Contact and enforce version 336 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 337 source.buildAssertWrapper(diff); 338 source.buildDiffWrapper(diff); 339 assertEquals("Unexpected operations", 2, diff.size()); 340 { 341 final CPOWrapper cpoWrapper = diff.get(0); 342 final ContentProviderOperation oper = cpoWrapper.getOperation(); 343 assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper)); 344 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 345 } 346 } 347 348 public void testEntityDiffInsertInsert() { 349 // Insert a RawContact 350 final ContentValues after = new ContentValues(); 351 after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); 352 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 353 354 final ValuesDelta values = ValuesDelta.fromAfter(after); 355 final RawContactDelta source = new RawContactDelta(values); 356 357 // Insert a new phone number 358 final ContentValues phone = new ContentValues(); 359 phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 360 phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); 361 phone.put(Phone.TYPE, Phone.TYPE_WORK); 362 source.addEntry(ValuesDelta.fromAfter(phone)); 363 364 // Assert two operations: delete Contact and enforce version 365 final ArrayList<CPOWrapper> diff = Lists.newArrayList(); 366 source.buildAssertWrapper(diff); 367 source.buildDiffWrapper(diff); 368 assertEquals("Unexpected operations", 3, diff.size()); 369 { 370 final CPOWrapper cpoWrapper = diff.get(0); 371 final ContentProviderOperation oper = cpoWrapper.getOperation(); 372 assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper)); 373 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 374 } 375 { 376 final CPOWrapper cpoWrapper = diff.get(1); 377 final ContentProviderOperation oper = cpoWrapper.getOperation(); 378 assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper)); 379 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 380 381 } 382 } 383} 384