Home | History | Annotate | Download | only in contacts
      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
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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