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