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