Home | History | Annotate | Download | only in contacts
      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.providers.contacts;
     18 
     19 import android.accounts.Account;
     20 import android.app.SearchManager;
     21 import android.content.ContentValues;
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.provider.ContactsContract;
     25 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     26 import android.provider.ContactsContract.Contacts;
     27 import android.provider.ContactsContract.Data;
     28 import android.provider.ContactsContract.StatusUpdates;
     29 import android.test.suitebuilder.annotation.MediumTest;
     30 
     31 import com.android.providers.contacts.testutil.DataUtil;
     32 import com.android.providers.contacts.testutil.RawContactUtil;
     33 
     34 /**
     35  * Unit tests for {@link GlobalSearchSupport}.
     36  * <p>
     37  * Run the test like this:
     38  * <p>
     39  * <code><pre>
     40  * adb shell am instrument -e class com.android.providers.contacts.GlobalSearchSupportTest -w \
     41  *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
     42  * </pre></code>
     43  */
     44 @MediumTest
     45 public class GlobalSearchSupportTest extends BaseContactsProvider2Test {
     46 
     47     public void testSearchSuggestionsNotInDefaultDirectory() throws Exception {
     48         Account account = new Account("actname", "acttype");
     49 
     50         // Creating an AUTO_ADD group will exclude all ungrouped contacts from global search
     51         createGroup(account, "any", "any", 0 /* visible */, true /* auto-add */, false /* fav */);
     52 
     53         long rawContactId = RawContactUtil.createRawContact(mResolver, account);
     54         DataUtil.insertStructuredName(mResolver, rawContactId, "Deer", "Dough");
     55 
     56         // Remove the new contact from all groups
     57         mResolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=" + rawContactId
     58                 + " AND " + Data.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE + "'", null);
     59 
     60         Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
     61                 .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath("D").build();
     62 
     63         // If the contact is not in the "my contacts" group, nothing should be found
     64         Cursor c = mResolver.query(searchUri, null, null, null, null);
     65         assertEquals(0, c.getCount());
     66         c.close();
     67     }
     68 
     69     public void testSearchSuggestionsByNameWithPhoto() throws Exception {
     70         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").photo(
     71                 loadTestPhoto()).build();
     72         new SuggestionTesterBuilder(contact).query("D").expectIcon1Uri(true).expectedText1(
     73                 "Deer Dough").build().test();
     74     }
     75 
     76     public void testSearchSuggestionsByEmailWithPhoto() {
     77         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").photo(
     78                 loadTestPhoto()).email("foo (at) acme.com").build();
     79         new SuggestionTesterBuilder(contact).query("foo@ac").expectIcon1Uri(true).expectedIcon2(
     80                 String.valueOf(StatusUpdates.getPresenceIconResourceId(StatusUpdates.OFFLINE)))
     81                 .expectedText1("Deer Dough").expectedText2("foo (at) acme.com").build().test();
     82     }
     83 
     84     public void testSearchSuggestionsByName() {
     85         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").company("Google")
     86                 .build();
     87         new SuggestionTesterBuilder(contact).query("D").expectedText1("Deer Dough").expectedText2(
     88                 null).build().test();
     89     }
     90 
     91     public void testSearchByNickname() {
     92         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").nickname(
     93                 "Little Fawn").company("Google").build();
     94         new SuggestionTesterBuilder(contact).query("L").expectedText1("Deer Dough").expectedText2(
     95                 "Little Fawn").build().test();
     96     }
     97 
     98     public void testSearchByCompany() {
     99         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").company("Google")
    100                 .build();
    101         new SuggestionTesterBuilder(contact).query("G").expectedText1("Deer Dough").expectedText2(
    102                 "Google").build().test();
    103     }
    104 
    105     public void testSearchByTitleWithCompany() {
    106         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").company("Google")
    107                 .title("Software Engineer").build();
    108         new SuggestionTesterBuilder(contact).query("S").expectIcon1Uri(false).expectedText1(
    109                 "Deer Dough").expectedText2("Software Engineer, Google").build().test();
    110     }
    111 
    112     public void testSearchSuggestionsByPhoneNumberOnNonPhone() throws Exception {
    113         getContactsProvider().setIsPhone(false);
    114 
    115         GoldenContact contact = new GoldenContactBuilder().name("Deer", "Dough").photo(
    116                 loadTestPhoto()).phone("1-800-4664-411").build();
    117         new SuggestionTesterBuilder(contact).query("1800").expectIcon1Uri(true).expectedText1(
    118                 "Deer Dough").expectedText2("1-800-4664-411").build().test();
    119     }
    120 
    121     /**
    122      * Tests that the quick search suggestion returns the expected contact
    123      * information.
    124      */
    125     private final class SuggestionTester {
    126 
    127         private final GoldenContact contact;
    128 
    129         private final String query;
    130 
    131         private final boolean expectIcon1Uri;
    132 
    133         private final String expectedIcon2;
    134 
    135         private final String expectedText1;
    136 
    137         private final String expectedText2;
    138 
    139         public SuggestionTester(SuggestionTesterBuilder builder) {
    140             contact = builder.contact;
    141             query = builder.query;
    142             expectIcon1Uri = builder.expectIcon1Uri;
    143             expectedIcon2 = builder.expectedIcon2;
    144             expectedText1 = builder.expectedText1;
    145             expectedText2 = builder.expectedText2;
    146         }
    147 
    148         /**
    149          * Tests suggest and refresh queries from quick search box, then deletes the contact from
    150          * the data base.
    151          */
    152         public void test() {
    153 
    154             testQsbSuggest();
    155             testContactIdQsbRefresh();
    156             testLookupKeyQsbRefresh();
    157 
    158             // Cleanup
    159             contact.delete();
    160         }
    161 
    162         /**
    163          * Tests that the contacts provider return the appropriate information from the golden
    164          * contact in response to the suggestion query from the quick search box.
    165          */
    166         private void testQsbSuggest() {
    167 
    168             Uri searchUri = new Uri.Builder().scheme("content").authority(
    169                     ContactsContract.AUTHORITY).appendPath(SearchManager.SUGGEST_URI_PATH_QUERY)
    170                     .appendPath(query).build();
    171 
    172             Cursor c = mResolver.query(searchUri, null, null, null, null);
    173             assertEquals(1, c.getCount());
    174             c.moveToFirst();
    175 
    176             String icon1 = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1));
    177             if (expectIcon1Uri) {
    178                 assertTrue(icon1.startsWith("content:"));
    179             } else {
    180                 assertEquals(String.valueOf(com.android.internal.R.drawable.ic_contact_picture),
    181                         icon1);
    182             }
    183 
    184             // SearchManager does not declare a constant for _id
    185             ContentValues values = getContactValues();
    186             assertCursorValues(c, values);
    187 
    188             c.close();
    189         }
    190 
    191         /**
    192          * Returns the expected Quick Search Box content values for the golden contact.
    193          */
    194         private ContentValues getContactValues() {
    195 
    196             ContentValues values = new ContentValues();
    197             values.put("_id", contact.getContactId());
    198             values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, expectedText1);
    199             values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, expectedText2);
    200 
    201             values.put(SearchManager.SUGGEST_COLUMN_ICON_2, expectedIcon2);
    202             values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA,
    203                     Contacts.getLookupUri(contact.getContactId(), contact.getLookupKey())
    204                             .toString());
    205             values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, contact.getLookupKey());
    206             values.put(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, query);
    207             return values;
    208         }
    209 
    210         /**
    211          * Returns the expected Quick Search Box content values for the golden contact.
    212          */
    213         private ContentValues getRefreshValues() {
    214 
    215             ContentValues values = new ContentValues();
    216             values.put("_id", contact.getContactId());
    217             values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, expectedText1);
    218             values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, expectedText2);
    219 
    220             values.put(SearchManager.SUGGEST_COLUMN_ICON_2, expectedIcon2);
    221             values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, contact.getLookupKey());
    222             values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, contact.getLookupKey());
    223             return values;
    224         }
    225 
    226         /**
    227          * Performs the refresh query and returns a cursor to the results.
    228          *
    229          * @param refreshId the final component path of the refresh query, which identifies which
    230          *        contact to refresh.
    231          */
    232         private Cursor refreshQuery(String refreshId) {
    233 
    234             // See if the same result is returned by a shortcut refresh
    235             Uri refershUri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath(
    236                     SearchManager.SUGGEST_URI_PATH_SHORTCUT)
    237                     .appendPath(refreshId)
    238                     .appendQueryParameter(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, query)
    239                     .build();
    240 
    241             String[] projection = new String[] {
    242                     SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_ICON_2,
    243                     SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2,
    244                     SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID,
    245                     SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "_id",
    246             };
    247 
    248             return mResolver.query(refershUri, projection, null, null, null);
    249         }
    250 
    251         /**
    252          * Tests that the contacts provider returns an empty result in response to a refresh query
    253          * from the quick search box that uses the contact id to identify the contact.  The empty
    254          * result indicates that the shortcut is no longer valid, and the QSB will replace it with
    255          * a new-style shortcut the next time they click on the contact.
    256          *
    257          * @see #testLookupKeyQsbRefresh()
    258          */
    259         private void testContactIdQsbRefresh() {
    260 
    261             Cursor c = refreshQuery(String.valueOf(contact.getContactId()));
    262             try {
    263                 assertEquals("Record count", 0, c.getCount());
    264             } finally {
    265                 c.close();
    266             }
    267         }
    268 
    269         /**
    270          * Tests that the contacts provider return the appropriate information from the golden
    271          * contact in response to the refresh query from the quick search box.  The refresh query
    272          * uses the currently-supported mechanism of identifying the contact by the lookup key,
    273          * which is more stable than the previously used contact id.
    274          */
    275         private void testLookupKeyQsbRefresh() {
    276 
    277             Cursor c = refreshQuery(contact.getLookupKey());
    278             try {
    279                 assertEquals("Record count", 1, c.getCount());
    280                 c.moveToFirst();
    281                 assertCursorValues(c, getRefreshValues());
    282             } finally {
    283                 c.close();
    284             }
    285         }
    286     }
    287 
    288     /**
    289      * Builds {@link SuggestionTester} objects. Unspecified boolean objects default to
    290      * false. Unspecified String objects default to null.
    291      */
    292     private final class SuggestionTesterBuilder {
    293 
    294         private final GoldenContact contact;
    295 
    296         private String query;
    297 
    298         private boolean expectIcon1Uri;
    299 
    300         private String expectedIcon2;
    301 
    302         private String expectedText1;
    303 
    304         private String expectedText2;
    305 
    306         public SuggestionTesterBuilder(GoldenContact contact) {
    307             this.contact = contact;
    308         }
    309 
    310         /**
    311          * Builds the {@link SuggestionTester} specified by this builder.
    312          */
    313         public SuggestionTester build() {
    314             return new SuggestionTester(this);
    315         }
    316 
    317         /**
    318          * The text of the user's query to quick search (i.e., what they typed
    319          * in the search box).
    320          */
    321         public SuggestionTesterBuilder query(String value) {
    322             query = value;
    323             return this;
    324         }
    325 
    326         /**
    327          * Whether to set Icon1, which in practice is the contact's photo.
    328          * <p>
    329          * TODO(tomo): Replace with actual expected value? This might be hard
    330          * because the values look non-deterministic, such as
    331          * "content://com.android.contacts/contacts/2015/photo"
    332          */
    333         public SuggestionTesterBuilder expectIcon1Uri(boolean value) {
    334             expectIcon1Uri = value;
    335             return this;
    336         }
    337 
    338         /**
    339          * The value for Icon2, which in practice is the contact's Chat status
    340          * (available, busy, etc.)
    341          */
    342         public SuggestionTesterBuilder expectedIcon2(String value) {
    343             expectedIcon2 = value;
    344             return this;
    345         }
    346 
    347         /**
    348          * First line of suggestion text expected to be returned (required).
    349          */
    350         public SuggestionTesterBuilder expectedText1(String value) {
    351             expectedText1 = value;
    352             return this;
    353         }
    354 
    355         /**
    356          * Second line of suggestion text expected to return (optional).
    357          */
    358         public SuggestionTesterBuilder expectedText2(String value) {
    359             expectedText2 = value;
    360             return this;
    361         }
    362     }
    363 }
    364