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 
     20 import android.content.ContentProviderClient;
     21 import android.content.ContentResolver;
     22 import android.content.ContentUris;
     23 import android.content.ContentValues;
     24 import android.net.Uri;
     25 import android.provider.ContactsContract;
     26 import android.provider.ContactsContract.CommonDataKinds.Email;
     27 import android.provider.ContactsContract.CommonDataKinds.Phone;
     28 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     29 import android.provider.ContactsContract.Contacts;
     30 import android.provider.ContactsContract.Data;
     31 import android.provider.ContactsContract.DataUsageFeedback;
     32 import android.provider.ContactsContract.RawContacts;
     33 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
     34 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
     35 import android.test.InstrumentationTestCase;
     36 
     37 /**
     38  * CTS tests for {@link android.provider.ContactsContract.Contacts#CONTENT_FREQUENT_URI},
     39  * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} and
     40  * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} apis.
     41  */
     42 public class ContactsContract_FrequentsStrequentsTest extends InstrumentationTestCase {
     43     private ContentResolver mResolver;
     44     private ContactsContract_TestDataBuilder mBuilder;
     45 
     46     private static final String[] STREQUENT_PROJECTION = new String[]{
     47             Contacts._ID,
     48             Contacts.HAS_PHONE_NUMBER,
     49             Contacts.NAME_RAW_CONTACT_ID,
     50             Contacts.IS_USER_PROFILE,
     51             Contacts.CUSTOM_RINGTONE,
     52             Contacts.DISPLAY_NAME,
     53             Contacts.DISPLAY_NAME_ALTERNATIVE,
     54             Contacts.DISPLAY_NAME_SOURCE,
     55             Contacts.IN_DEFAULT_DIRECTORY,
     56             Contacts.IN_VISIBLE_GROUP,
     57             Contacts.LAST_TIME_CONTACTED,
     58             Contacts.LOOKUP_KEY,
     59             Contacts.PHONETIC_NAME,
     60             Contacts.PHONETIC_NAME_STYLE,
     61             Contacts.PHOTO_ID,
     62             Contacts.PHOTO_FILE_ID,
     63             Contacts.PHOTO_URI,
     64             Contacts.PHOTO_THUMBNAIL_URI,
     65             Contacts.SEND_TO_VOICEMAIL,
     66             Contacts.SORT_KEY_ALTERNATIVE,
     67             Contacts.SORT_KEY_PRIMARY,
     68             Contacts.STARRED,
     69             Contacts.PINNED,
     70             Contacts.TIMES_CONTACTED,
     71             Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
     72             Contacts.CONTACT_PRESENCE,
     73             Contacts.CONTACT_CHAT_CAPABILITY,
     74             Contacts.CONTACT_STATUS,
     75             Contacts.CONTACT_STATUS_TIMESTAMP,
     76             Contacts.CONTACT_STATUS_RES_PACKAGE,
     77             Contacts.CONTACT_STATUS_LABEL,
     78             Contacts.CONTACT_STATUS_ICON,
     79             Data.TIMES_USED,
     80             Data.LAST_TIME_USED,
     81     };
     82 
     83     private static final String[] STREQUENT_PHONE_ONLY_PROJECTION = new String[]{
     84             Data._ID,
     85             Contacts.HAS_PHONE_NUMBER,
     86             Contacts.NAME_RAW_CONTACT_ID,
     87             Contacts.IS_USER_PROFILE,
     88             Contacts.CUSTOM_RINGTONE,
     89             Contacts.DISPLAY_NAME,
     90             Contacts.DISPLAY_NAME_ALTERNATIVE,
     91             Contacts.DISPLAY_NAME_SOURCE,
     92             Contacts.IN_DEFAULT_DIRECTORY,
     93             Contacts.IN_VISIBLE_GROUP,
     94             Contacts.LAST_TIME_CONTACTED,
     95             Contacts.LOOKUP_KEY,
     96             Contacts.PHONETIC_NAME,
     97             Contacts.PHONETIC_NAME_STYLE,
     98             Contacts.PHOTO_ID,
     99             Contacts.PHOTO_FILE_ID,
    100             Contacts.PHOTO_URI,
    101             Contacts.PHOTO_THUMBNAIL_URI,
    102             Contacts.SEND_TO_VOICEMAIL,
    103             Contacts.SORT_KEY_ALTERNATIVE,
    104             Contacts.SORT_KEY_PRIMARY,
    105             Contacts.STARRED,
    106             Contacts.PINNED,
    107             Contacts.TIMES_CONTACTED,
    108             Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
    109             Contacts.CONTACT_PRESENCE,
    110             Contacts.CONTACT_CHAT_CAPABILITY,
    111             Contacts.CONTACT_STATUS,
    112             Contacts.CONTACT_STATUS_TIMESTAMP,
    113             Contacts.CONTACT_STATUS_RES_PACKAGE,
    114             Contacts.CONTACT_STATUS_LABEL,
    115             Contacts.CONTACT_STATUS_ICON,
    116             Data.TIMES_USED,
    117             Data.LAST_TIME_USED,
    118             Phone.NUMBER,
    119             Phone.TYPE,
    120             Phone.LABEL,
    121             Phone.IS_SUPER_PRIMARY,
    122             Phone.CONTACT_ID,
    123     };
    124 
    125     public static ContentValues[] sContentValues = new ContentValues[3];
    126     static {
    127         ContentValues cv1 = new ContentValues();
    128         cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale");
    129         sContentValues[0] = cv1;
    130 
    131         ContentValues cv2 = new ContentValues();
    132         cv2.put(Contacts.DISPLAY_NAME, "Cold Tamago");
    133         sContentValues[1] = cv2;
    134 
    135         ContentValues cv3 = new ContentValues();
    136         cv3.put(Contacts.DISPLAY_NAME, "John Doe");
    137         sContentValues[2] = cv3;
    138     }
    139 
    140     private long[] mDataIds = new long[3];
    141 
    142     @Override
    143     protected void setUp() throws Exception {
    144         super.setUp();
    145         mResolver = getInstrumentation().getTargetContext().getContentResolver();
    146         ContentProviderClient provider =
    147                 mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
    148         mBuilder = new ContactsContract_TestDataBuilder(provider);
    149     }
    150 
    151     @Override
    152     protected void tearDown() throws Exception {
    153         super.tearDown();
    154         mBuilder.cleanup();
    155     }
    156 
    157     /**
    158      * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
    159      * no contacts if there are no starred or frequent contacts in the user's contacts.
    160      */
    161     public void testStrequents_noStarredOrFrequents() throws Exception {
    162         long[] ids = setupTestData();
    163         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids, false);
    164     }
    165 
    166     /**
    167      * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
    168      * starred contacts in the correct order if there are only starred contacts in the user's
    169      * contacts.
    170      */
    171     public void testStrequents_starredOnlyInCorrectOrder() throws Exception {
    172         long[] ids = setupTestData();
    173 
    174         // Star/favorite the first and third contact.
    175         starContact(ids[0]);
    176         starContact(ids[1]);
    177 
    178         // Only the starred contacts should be returned, ordered alphabetically by name
    179         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
    180                 false, sContentValues[1], sContentValues[0]);
    181     }
    182 
    183     /**
    184      * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
    185      * frequent contacts in the correct order if there are only frequent contacts in the user's
    186      * contacts.
    187      */
    188     public void testStrequents_frequentsOnlyInCorrectOrder() throws Exception {
    189         long[] ids = setupTestData();
    190 
    191         // Contact the first contact once.
    192         markDataAsUsed(mDataIds[0], 1);
    193 
    194         // Contact the second contact thrice.
    195         markDataAsUsed(mDataIds[1], 3);
    196 
    197         // Contact the third contact twice.
    198         markDataAsUsed(mDataIds[2], 2);
    199 
    200         // The strequents uri should now return contact 2, 3, 1 in order due to ranking by
    201         // data usage.
    202         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
    203                 false, sContentValues[1], sContentValues[2], sContentValues[0]);
    204     }
    205 
    206     /**
    207      * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns
    208      * first starred, then frequent contacts in their respective correct orders if there are both
    209      * starred and frequent contacts in the user's contacts.
    210      */
    211     public void testStrequents_starredAndFrequentsInCorrectOrder() throws Exception {
    212         long[] ids = setupTestData();
    213 
    214         // Contact the first contact once.
    215         markDataAsUsed(mDataIds[0], 1);
    216 
    217         // Contact the second contact thrice.
    218         markDataAsUsed(mDataIds[1], 3);
    219 
    220         // Contact the third contact twice, and mark it as used
    221         markDataAsUsed(mDataIds[2], 2);
    222         starContact(ids[2]);
    223 
    224         // The strequents uri should now return contact 3, 2, 1 in order. Contact 3 is ranked first
    225         // because it is starred, followed by contacts 2 and 1 due to their data usage ranking.
    226         // Note that contact 3 is only returned once (as a starred contact) even though it is also
    227         // a frequently contacted contact.
    228         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids,
    229                 false, sContentValues[2], sContentValues[1], sContentValues[0]);
    230     }
    231 
    232     /**
    233      * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI}
    234      * correctly filters the returned contacts with the given user input.
    235      */
    236     public void testStrequents_withFilter() throws Exception {
    237         long[] ids = setupTestData();
    238 
    239         //Star all 3 contacts
    240         starContact(ids[0]);
    241         starContact(ids[1]);
    242         starContact(ids[2]);
    243 
    244         // Construct a uri that filters for the query string "ta".
    245         Uri uri = Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon().appendEncodedPath("ta").build();
    246 
    247         // Only contact 1 and 2 should be returned (sorted in alphabetical order) due to the
    248         // filtered query.
    249         assertCursorStoredValuesWithContactsFilter(uri, ids, false, sContentValues[1], sContentValues[0]);
    250     }
    251 
    252     public void testStrequents_projection() throws Exception {
    253         long[] ids = setupTestData();
    254 
    255         // Start contact 0 and mark contact 2 as frequent
    256         starContact(ids[0]);
    257         markDataAsUsed(mDataIds[2], 1);
    258 
    259         DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_STREQUENT_URI,
    260                 STREQUENT_PROJECTION,
    261                 new long[]{ids[0], ids[2]}
    262         );
    263 
    264         // Strequent filter.
    265         DatabaseAsserts.checkProjection(mResolver,
    266                 Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon()
    267                         .appendEncodedPath("Hot Tamale").build(),
    268                 STREQUENT_PROJECTION,
    269                 new long[]{ids[0]}
    270         );
    271     }
    272 
    273     public void testStrequents_phoneOnly() throws Exception {
    274         long[] ids = setupTestData();
    275 
    276         // Star all 3 contacts
    277         starContact(ids[0]);
    278         starContact(ids[1]);
    279         starContact(ids[2]);
    280 
    281         // Construct a uri for phone only favorites.
    282         Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
    283                 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
    284 
    285         // Only the contacts with phone numbers are returned, in alphabetical order. Filtering
    286         // is done with data ids instead of contact ids since each row contains a single data item.
    287         assertCursorStoredValuesWithContactsFilter(uri, mDataIds, false,
    288                 sContentValues[0], sContentValues[2]);
    289     }
    290 
    291     public void testStrequents_phoneOnlyFrequentsOrder() throws Exception {
    292         long[] ids = setupTestData();
    293 
    294         // Contact the first contact once.
    295         markDataAsUsed(mDataIds[0], 1);
    296 
    297         // Contact the second contact twice.
    298         markDataAsUsed(mDataIds[1], 2);
    299 
    300         // Contact the third contact thrice.
    301         markDataAsUsed(mDataIds[2], 3);
    302 
    303         // Construct a uri for phone only favorites.
    304         Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
    305                 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
    306 
    307         // Only the contacts with phone numbers are returned, in frequency ranking order.
    308         assertCursorStoredValuesWithContactsFilter(uri, mDataIds, false,
    309                 sContentValues[2], sContentValues[0]);
    310     }
    311 
    312     public void testStrequents_phoneOnly_projection() throws Exception {
    313         long[] ids = setupTestData();
    314 
    315         // Start contact 0 and mark contact 2 as frequent
    316         starContact(ids[0]);
    317         markDataAsUsed(mDataIds[2], 1);
    318 
    319         // Construct a uri for phone only favorites.
    320         Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon().
    321                 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
    322 
    323         DatabaseAsserts.checkProjection(mResolver, uri,
    324                 STREQUENT_PHONE_ONLY_PROJECTION,
    325                 new long[]{mDataIds[0], mDataIds[2]} // Note _id from phone_only is data._id
    326         );
    327     }
    328 
    329     public void testFrequents_noFrequentsReturnsEmptyCursor() throws Exception {
    330         long[] ids = setupTestData();
    331         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_FREQUENT_URI, ids, false);
    332     }
    333 
    334     public void testFrequents_CorrectOrder() throws Exception {
    335         long[] ids = setupTestData();
    336 
    337         // Contact the first contact once.
    338         markDataAsUsed(mDataIds[0], 1);
    339 
    340         // Contact the second contact thrice.
    341         markDataAsUsed(mDataIds[1], 3);
    342 
    343         // Contact the third contact twice.
    344         markDataAsUsed(mDataIds[2], 2);
    345 
    346         // The frequents uri should now return contact 2, 3, 1 in order due to ranking by
    347         // data usage.
    348         assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_FREQUENT_URI, ids,
    349                 true /* inOrder */, sContentValues[1], sContentValues[2], sContentValues[0]);
    350     }
    351 
    352     public void testFrequent_projection() throws Exception {
    353         long[] ids = setupTestData();
    354 
    355         markDataAsUsed(mDataIds[0], 10);
    356 
    357         DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_FREQUENT_URI,
    358                 STREQUENT_PROJECTION,
    359                 new long[]{ids[0]}
    360         );
    361     }
    362 
    363     /**
    364      * Given a uri, performs a query on the contacts provider for that uri and asserts that the
    365      * cursor returned from the query matches the expected results.
    366      *
    367      * @param uri Uri to perform the query for
    368      * @param contactsId Array of contact IDs that serves as an additional filter on the result
    369      * set. This is needed to limit the output to temporary test contacts that were created for
    370      * purposes of the test, so that the tests do not fail on devices with existing contacts on
    371      * them
    372      * @param inOrder Whether or not the returned rows in the cursor should correspond to the
    373      * order of the provided ContentValues
    374      * @param expected An array of ContentValues corresponding to the expected output of the query
    375      */
    376     private void assertCursorStoredValuesWithContactsFilter(Uri uri, long[] contactsId,
    377             boolean inOrder, ContentValues... expected) {
    378         // We need this helper function to add a filter for specific contacts because
    379         // otherwise tests will fail if performed on a device with existing contacts data
    380         StringBuilder sb = new StringBuilder();
    381         sb.append(Contacts._ID + " in ");
    382         sb.append("(");
    383         for (int i = 0; i < contactsId.length; i++) {
    384             if (i != 0) sb.append(",");
    385             sb.append(contactsId[i]);
    386         }
    387         sb.append(")");
    388         DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null, sb.toString(),
    389                 null, null, inOrder, expected);
    390     }
    391 
    392     /**
    393      * Given a contact id, update the contact corresponding to that contactId so that it now shows
    394      * up in the user's favorites/starred contacts.
    395      *
    396      * @param contactId Contact ID corresponding to the contact to star
    397      */
    398     private void starContact(long contactId) {
    399         ContentValues values = new ContentValues();
    400         values.put(Contacts.STARRED, 1);
    401         mResolver.update(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), values,
    402                 null, null);
    403     }
    404 
    405     /**
    406      * Given a data id, increment the data usage stats by a given number of usages to simulate
    407      * the user making a call to the given data item.
    408      *
    409      * @param dataId Id of the data item to increment data usage stats for
    410      * @param numTimes The number of times to increase the data usage stats by
    411      */
    412     private void markDataAsUsed(long dataId, int numTimes) {
    413         Uri uri = ContactsContract.DataUsageFeedback.FEEDBACK_URI.buildUpon().
    414                 appendPath(String.valueOf(dataId)).
    415                 appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
    416                         DataUsageFeedback.USAGE_TYPE_CALL).build();
    417         for (int i = 1; i <= numTimes; i++) {
    418             mResolver.update(uri, new ContentValues(), null, null);
    419         }
    420     }
    421 
    422     /**
    423      * Setup the contacts database with temporary contacts used for testing. These contacts will
    424      * be removed during teardown.
    425      *
    426      * @return An array of long values corresponding to the ids of the created contacts
    427      *
    428      * @throws Exception
    429      */
    430     private long[] setupTestData() throws Exception {
    431         TestRawContact rawContact = mBuilder.newRawContact()
    432                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
    433                 .with(RawContacts.ACCOUNT_NAME, "test_name")
    434                 .insert();
    435         rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
    436                 .with(StructuredName.DISPLAY_NAME, "Hot Tamale")
    437                 .insert();
    438         mDataIds[0] = rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
    439                 .with(Phone.DATA, "510-123-5769")
    440                 .with(Email.TYPE, Phone.TYPE_HOME)
    441                 .insert().load().getId();
    442         rawContact.load();
    443         TestContact contact = rawContact.getContact().load();
    444 
    445         TestRawContact rawContact2 = mBuilder.newRawContact()
    446                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
    447                 .with(RawContacts.ACCOUNT_NAME, "test_name")
    448                 .insert();
    449         rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
    450                 .with(StructuredName.DISPLAY_NAME, "Cold Tamago")
    451                 .insert();
    452         mDataIds[1] = rawContact2.newDataRow(Email.CONTENT_ITEM_TYPE)
    453                 .with(Email.DATA, "eggs (at) farmers.org")
    454                 .with(Email.TYPE, Email.TYPE_HOME)
    455                 .insert().load().getId();
    456         rawContact2.load();
    457         TestContact contact2 = rawContact2.getContact().load();
    458 
    459         TestRawContact rawContact3 = mBuilder.newRawContact()
    460                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
    461                 .with(RawContacts.ACCOUNT_NAME, "test_name")
    462                 .insert();
    463         rawContact3.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
    464                 .with(StructuredName.DISPLAY_NAME, "John Doe")
    465                 .insert();
    466         mDataIds[2] = rawContact3.newDataRow(Phone.CONTENT_ITEM_TYPE)
    467                 .with(Phone.DATA, "518-354-1111")
    468                 .with(Phone.TYPE, Phone.TYPE_HOME)
    469                 .insert().load().getId();
    470         rawContact3.load();
    471         TestContact contact3 = rawContact3.getContact().load();
    472 
    473         return new long[] {contact.getId(), contact2.getId(), contact3.getId()};
    474     }
    475 }
    476 
    477