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