1 /*
2  * Copyright (C) 2013 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 android.provider.cts.contacts;
18 
19 import static android.provider.cts.contacts.ContactUtil.newContentValues;
20 
21 import android.content.ContentProviderOperation;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.OperationApplicationException;
26 import android.net.Uri;
27 import android.os.RemoteException;
28 import android.provider.ContactsContract;
29 import android.provider.ContactsContract.AggregationExceptions;
30 import android.provider.ContactsContract.Contacts;
31 import android.provider.ContactsContract.PinnedPositions;
32 import android.provider.ContactsContract.RawContacts;
33 import android.provider.cts.contacts.CommonDatabaseUtils;
34 import android.provider.cts.contacts.ContactUtil;
35 import android.provider.cts.contacts.DatabaseAsserts;
36 import android.provider.cts.contacts.RawContactUtil;
37 import android.test.AndroidTestCase;
38 import android.util.Log;
39 
40 import java.util.ArrayList;
41 
42 /**
43  * CTS tests for {@link android.provider.ContactsContract.PinnedPositions} API
44  */
45 public class ContactsContract_PinnedPositionsTest extends AndroidTestCase {
46     private static final String TAG = "ContactsContract_PinnedPositionsTest";
47 
48     private ContentResolver mResolver;
49 
50     @Override
setUp()51     protected void setUp() throws Exception {
52         super.setUp();
53         mResolver = getContext().getContentResolver();
54     }
55 
56     /**
57      * Tests that the ContactsProvider automatically stars/unstars a pinned/unpinned contact if
58      * {@link PinnedPositions#STAR_WHEN_PINNING} boolean parameter is set to true, and that the
59      * values are correctly propogated to the contact's constituent raw contacts.
60      */
testPinnedPositionsUpdate()61     public void testPinnedPositionsUpdate() {
62         final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
63         final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
64         final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
65         final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
66 
67         final int unpinned = PinnedPositions.UNPINNED;
68 
69         assertValuesForContact(i1.mContactId,
70                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
71         assertValuesForContact(i2.mContactId,
72                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
73         assertValuesForContact(i3.mContactId,
74                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
75         assertValuesForContact(i4.mContactId,
76                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
77 
78         assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
79         assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
80         assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
81         assertValuesForRawContact(i4.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
82 
83         final ArrayList<ContentProviderOperation> operations =
84                 new ArrayList<ContentProviderOperation>();
85         operations.add(newPinningOperation(i1.mContactId, 1, true));
86         operations.add(newPinningOperation(i3.mContactId, 3, true));
87         operations.add(newPinningOperation(i4.mContactId, 2, false));
88         applyBatch(mResolver, operations);
89 
90         assertValuesForContact(i1.mContactId,
91                 newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
92         assertValuesForContact(i2.mContactId,
93                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
94         assertValuesForContact(i3.mContactId,
95                 newContentValues(Contacts.PINNED, 3, Contacts.STARRED, 1));
96         assertValuesForContact(i4.mContactId,
97                 newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 0));
98 
99         // Make sure the values are propagated to raw contacts.
100         assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, 1));
101         assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, unpinned));
102         assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, 3));
103         assertValuesForRawContact(i4.mRawContactId, newContentValues(RawContacts.PINNED, 2));
104 
105         operations.clear();
106 
107         // Now unpin the contact
108         operations.add(newPinningOperation(i3.mContactId, unpinned, false));
109         applyBatch(mResolver, operations);
110 
111         assertValuesForContact(i1.mContactId,
112                 newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
113         assertValuesForContact(i2.mContactId,
114                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
115         assertValuesForContact(i3.mContactId,
116                 newContentValues(Contacts.PINNED, unpinned, Contacts.STARRED, 0));
117         assertValuesForContact(i4.mContactId,
118                 newContentValues(Contacts.PINNED, 2, Contacts.STARRED, 0));
119 
120         assertValuesForRawContact(i1.mRawContactId,
121                 newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
122         assertValuesForRawContact(i2.mRawContactId,
123                 newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
124         assertValuesForRawContact(i3.mRawContactId,
125                 newContentValues(RawContacts.PINNED, unpinned, RawContacts.STARRED, 0));
126         assertValuesForRawContact(i4.mRawContactId,
127                 newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 0));
128 
129         ContactUtil.delete(mResolver, i1.mContactId);
130         ContactUtil.delete(mResolver, i2.mContactId);
131         ContactUtil.delete(mResolver, i3.mContactId);
132         ContactUtil.delete(mResolver, i4.mContactId);
133     }
134 
135     /**
136      * Tests that pinned positions are correctly handled after the ContactsProvider aggregates
137      * and splits raw contacts.
138      */
testPinnedPositionsAfterJoinAndSplit()139     public void testPinnedPositionsAfterJoinAndSplit() {
140         final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
141         final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
142         final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver);
143         final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver);
144         final DatabaseAsserts.ContactIdPair i5 = DatabaseAsserts.assertAndCreateContact(mResolver);
145         final DatabaseAsserts.ContactIdPair i6 = DatabaseAsserts.assertAndCreateContact(mResolver);
146 
147         final ArrayList<ContentProviderOperation> operations =
148                 new ArrayList<ContentProviderOperation>();
149 
150         operations.add(newPinningOperation(i1.mContactId, 1, true));
151         operations.add(newPinningOperation(i2.mContactId, 2, true));
152         operations.add(newPinningOperation(i3.mContactId, 3, true));
153         operations.add(newPinningOperation(i5.mContactId, 5, true));
154         operations.add(newPinningOperation(i6.mContactId, 6, true));
155 
156         applyBatch(mResolver, operations);
157 
158         // Aggregate raw contact 1 and 4 together.
159         ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
160                 i1.mRawContactId, i4.mRawContactId);
161 
162         // If only one contact is pinned, the resulting contact should inherit the pinned position.
163         assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, 1));
164         assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
165         assertValuesForContact(i3.mContactId, newContentValues(Contacts.PINNED, 3));
166         assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 5));
167         assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
168 
169         assertValuesForRawContact(i1.mRawContactId,
170                 newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
171         assertValuesForRawContact(i2.mRawContactId,
172                 newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 1));
173         assertValuesForRawContact(i3.mRawContactId,
174                 newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 1));
175         assertValuesForRawContact(i4.mRawContactId,
176                 newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
177                         0));
178         assertValuesForRawContact(i5.mRawContactId,
179                 newContentValues(RawContacts.PINNED, 5, RawContacts.STARRED, 1));
180         assertValuesForRawContact(i6.mRawContactId,
181                 newContentValues(RawContacts.PINNED, 6, RawContacts.STARRED, 1));
182 
183         // Aggregate raw contact 2 and 3 together.
184         ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
185                 i2.mRawContactId, i3.mRawContactId);
186 
187         // If both raw contacts are pinned, the resulting contact should inherit the lower
188         // pinned position.
189         assertValuesForContact(i1.mContactId, newContentValues(Contacts.PINNED, 1));
190         assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
191         assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 5));
192         assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
193 
194         assertValuesForRawContact(i1.mRawContactId, newContentValues(RawContacts.PINNED, 1));
195         assertValuesForRawContact(i2.mRawContactId, newContentValues(RawContacts.PINNED, 2));
196         assertValuesForRawContact(i3.mRawContactId, newContentValues(RawContacts.PINNED, 3));
197         assertValuesForRawContact(i4.mRawContactId,
198                 newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED));
199         assertValuesForRawContact(i5.mRawContactId, newContentValues(RawContacts.PINNED, 5));
200         assertValuesForRawContact(i6.mRawContactId, newContentValues(RawContacts.PINNED, 6));
201 
202         // Split the aggregated raw contacts.
203         ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_SEPARATE,
204             i1.mRawContactId, i4.mRawContactId);
205 
206         // Raw contacts should keep the pinned position after re-grouping, and still starred.
207         assertValuesForRawContact(i1.mRawContactId,
208                 newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED,
209                         1));
210         assertValuesForRawContact(i2.mRawContactId,
211                 newContentValues(RawContacts.PINNED, 2, RawContacts.STARRED, 1));
212         assertValuesForRawContact(i3.mRawContactId,
213                 newContentValues(RawContacts.PINNED, 3, RawContacts.STARRED, 1));
214         assertValuesForRawContact(i4.mRawContactId,
215                 newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
216                         0));
217         assertValuesForRawContact(i5.mRawContactId,
218                 newContentValues(RawContacts.PINNED, 5, RawContacts.STARRED, 1));
219         assertValuesForRawContact(i6.mRawContactId,
220                 newContentValues(RawContacts.PINNED, 6, RawContacts.STARRED, 1));
221 
222         // Now demote contact 5.
223         operations.clear();
224         operations.add(newPinningOperation(i5.mContactId, PinnedPositions.DEMOTED, false));
225         applyBatch(mResolver, operations);
226 
227         // Get new contact Ids for contacts composing of raw contacts 1 and 4 because they have
228         // changed.
229         final long cId1 = RawContactUtil.queryContactIdByRawContactId(mResolver, i1.mRawContactId);
230         final long cId4 = RawContactUtil.queryContactIdByRawContactId(mResolver, i4.mRawContactId);
231 
232         assertValuesForContact(cId1, newContentValues(Contacts.PINNED, 1));
233         assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
234         assertValuesForContact(cId4, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
235         assertValuesForContact(i5.mContactId,
236                 newContentValues(Contacts.PINNED, PinnedPositions.DEMOTED));
237         assertValuesForContact(i6.mContactId, newContentValues(Contacts.PINNED, 6));
238 
239         // Aggregate contacts 5 and 6 together.
240         ContactUtil.setAggregationException(mResolver, AggregationExceptions.TYPE_KEEP_TOGETHER,
241                 i5.mRawContactId, i6.mRawContactId);
242 
243         // The resulting contact should have a pinned value of 6.
244         assertValuesForContact(cId1, newContentValues(Contacts.PINNED, 1));
245         assertValuesForContact(i2.mContactId, newContentValues(Contacts.PINNED, 2));
246         assertValuesForContact(cId4, newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED));
247         assertValuesForContact(i5.mContactId, newContentValues(Contacts.PINNED, 6));
248 
249         ContactUtil.delete(mResolver, cId1);
250         ContactUtil.delete(mResolver, i2.mContactId);
251         ContactUtil.delete(mResolver, cId4);
252         ContactUtil.delete(mResolver, i5.mContactId);
253     }
254 
255     /**
256      * Tests that calling {@link PinnedPositions#UNDEMOTE_METHOD} with an illegal argument correctly
257      * throws an IllegalArgumentException.
258      */
testPinnedPositionsDemoteIllegalArguments()259     public void testPinnedPositionsDemoteIllegalArguments() {
260         try {
261             mResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
262                     null, null);
263             fail();
264         } catch (IllegalArgumentException expected) {
265         }
266 
267         try {
268             mResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
269                     "1.1", null);
270             fail();
271         } catch (IllegalArgumentException expected) {
272         }
273 
274         try {
275             mResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD,
276                     "NotANumber", null);
277             fail();
278         } catch (IllegalArgumentException expected) {
279         }
280 
281         // Valid contact ID that does not correspond to an actual contact is silently ignored
282         mResolver.call(ContactsContract.AUTHORITY_URI, PinnedPositions.UNDEMOTE_METHOD, "999",
283                 null);
284     }
285 
286     /**
287      * Tests that pinned positions are correctly handled for contacts that have been demoted
288      * or undemoted.
289      */
testPinnedPositionsAfterDemoteAndUndemote()290     public void testPinnedPositionsAfterDemoteAndUndemote() {
291         final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver);
292         final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver);
293 
294         // Pin contact 1 and demote contact 2
295         final ArrayList<ContentProviderOperation> operations =
296                 new ArrayList<ContentProviderOperation>();
297         operations.add(newPinningOperation(i1.mContactId, 1, true));
298         operations.add(newPinningOperation(i2.mContactId, PinnedPositions.DEMOTED, false));
299         applyBatch(mResolver, operations);
300 
301         assertValuesForContact(i1.mContactId,
302                 newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
303         assertValuesForContact(i2.mContactId,
304                 newContentValues(Contacts.PINNED, PinnedPositions.DEMOTED, Contacts.STARRED, 0));
305 
306         assertValuesForRawContact(i1.mRawContactId,
307                 newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
308         assertValuesForRawContact(i2.mRawContactId,
309                 newContentValues(RawContacts.PINNED, PinnedPositions.DEMOTED, RawContacts.STARRED, 0));
310 
311         // Now undemote both contacts.
312         PinnedPositions.undemote(mResolver, i1.mContactId);
313         PinnedPositions.undemote(mResolver, i2.mContactId);
314 
315         // Contact 1 remains pinned at 0, while contact 2 becomes unpinned.
316         assertValuesForContact(i1.mContactId,
317                 newContentValues(Contacts.PINNED, 1, Contacts.STARRED, 1));
318         assertValuesForContact(i2.mContactId,
319                 newContentValues(Contacts.PINNED, PinnedPositions.UNPINNED, Contacts.STARRED, 0));
320 
321         assertValuesForRawContact(i1.mRawContactId,
322                 newContentValues(RawContacts.PINNED, 1, RawContacts.STARRED, 1));
323         assertValuesForRawContact(i2.mRawContactId,
324                 newContentValues(RawContacts.PINNED, PinnedPositions.UNPINNED, RawContacts.STARRED,
325                         0));
326 
327         ContactUtil.delete(mResolver, i1.mContactId);
328         ContactUtil.delete(mResolver, i2.mContactId);
329     }
330 
331     /**
332      * Verifies that the stored values for the contact that corresponds to the given contactId
333      * contain the exact same name-value pairs in the given ContentValues.
334      *
335      * @param contactId Id of a valid contact in the contacts database.
336      * @param contentValues A valid ContentValues object.
337      */
assertValuesForContact(long contactId, ContentValues contentValues)338     private void assertValuesForContact(long contactId, ContentValues contentValues) {
339         DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, Contacts.CONTENT_URI.
340                 buildUpon().appendEncodedPath(String.valueOf(contactId)).build(), contentValues);
341     }
342 
343     /**
344      * Verifies that the stored values for the raw contact that corresponds to the given
345      * rawContactId contain the exact same name-value pairs in the given ContentValues.
346      *
347      * @param rawContactId Id of a valid contact in the contacts database
348      * @param contentValues A valid ContentValues object
349      */
assertValuesForRawContact(long rawContactId, ContentValues contentValues)350     private void assertValuesForRawContact(long rawContactId, ContentValues contentValues) {
351         DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, RawContacts.CONTENT_URI.
352                 buildUpon().appendEncodedPath(String.valueOf(rawContactId)).build(), contentValues);
353     }
354 
355     /**
356      * Updates the contacts provider for a contact or raw contact corresponding to the given
357      * contact with key-value pairs as specified in the provided string parameters. Throws an
358      * exception if the number of provided string parameters is not zero or non-even.
359      *
360      * @param uri base URI that the provided ID will be appended onto, in order to creating the
361      * resulting URI
362      * @param id id of the contact of raw contact to perform the update for
363      * @param extras an even number of string parameters that correspond to name-value pairs
364      *
365      * @return the number of rows that were updated
366      */
updateItemForContact(Uri uri, long id, String... extras)367     private int updateItemForContact(Uri uri, long id, String... extras) {
368         Uri itemUri = ContentUris.withAppendedId(uri, id);
369         return updateItemForUri(itemUri, extras);
370     }
371 
372     /**
373      * Updates the contacts provider for the given YRU with key-value pairs as specified in the
374      * provided string parameters. Throws an exception if the number of provided string parameters
375      * is not zero or non-even.
376      *
377      * @param uri URI to perform the update for
378      * @param extras an even number of string parameters that correspond to name-value pairs
379      *
380      * @return the number of rows that were updated
381      */
updateItemForUri(Uri uri, String... extras)382     private int updateItemForUri(Uri uri, String... extras) {
383         ContentValues values = new ContentValues();
384         CommonDatabaseUtils.extrasVarArgsToValues(values, extras);
385         return mResolver.update(uri, values, null, null);
386     }
387 
newPinningOperation(long id, int pinned, boolean star)388     private ContentProviderOperation newPinningOperation(long id, int pinned, boolean star) {
389         final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(id));
390         final ContentValues values = new ContentValues();
391         values.put(Contacts.PINNED, pinned);
392         values.put(Contacts.STARRED, star ? 1 : 0);
393         return ContentProviderOperation.newUpdate(uri).withValues(values).build();
394     }
395 
applyBatch(ContentResolver resolver, ArrayList<ContentProviderOperation> operations)396     private static void applyBatch(ContentResolver resolver,
397             ArrayList<ContentProviderOperation> operations) {
398         try {
399             resolver.applyBatch(ContactsContract.AUTHORITY, operations);
400         } catch (OperationApplicationException e) {
401             Log.wtf(TAG, "ContentResolver batch operation failed.");
402         } catch (RemoteException e) {
403             Log.wtf(TAG, "Remote exception when performing batch operation.");
404         }
405     }
406 }
407 
408