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