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