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