OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/autocomplete/contact_provider_chromeos.h" |
| 6 |
| 7 #include <algorithm> |
| 8 |
| 9 #include "base/string_split.h" |
| 10 #include "base/utf_string_conversions.h" |
| 11 #include "chrome/browser/autocomplete/autocomplete_input.h" |
| 12 #include "chrome/browser/chromeos/contacts/contact.pb.h" |
| 13 #include "chrome/browser/chromeos/contacts/contact_manager.h" |
| 14 #include "chrome/browser/profiles/profile.h" |
| 15 #include "unicode/usearch.h" |
| 16 |
| 17 namespace { |
| 18 |
| 19 // Searches for the first occurrence of |substring| within |string|. Case and |
| 20 // accent differences are ignored -- see "primary level" at |
| 21 // http://userguide.icu-project.org/collation/concepts. |
| 22 // |
| 23 // false is returned if |substring| is not found or if an error occurs. If |
| 24 // non-NULL, |string_start_index| is set to the 0-based index where the match |
| 25 // begins and |string_match_length| is set to the length of the matched text. |
| 26 bool FindFirstSubstring(const string16& string, |
| 27 const string16& substring, |
| 28 size_t* string_start_index, |
| 29 size_t* string_match_length) { |
| 30 if (string.empty() || substring.empty()) |
| 31 return false; |
| 32 |
| 33 UErrorCode status = U_ZERO_ERROR; |
| 34 UStringSearch* search = usearch_open(substring.data(), -1, |
| 35 string.data(), -1, |
| 36 uloc_getDefault(), |
| 37 NULL, // breakiter |
| 38 &status); |
| 39 if (!U_SUCCESS(status)) |
| 40 return false; |
| 41 |
| 42 UCollator* collator = usearch_getCollator(search); |
| 43 ucol_setStrength(collator, UCOL_PRIMARY); |
| 44 usearch_reset(search); |
| 45 |
| 46 int32_t start_index = usearch_first(search, &status); |
| 47 int32_t match_length = usearch_getMatchedLength(search); |
| 48 usearch_close(search); |
| 49 |
| 50 if (!U_SUCCESS(status) || start_index == USEARCH_DONE) |
| 51 return false; |
| 52 |
| 53 DCHECK_GE(start_index, 0); |
| 54 DCHECK_GE(match_length, 0); |
| 55 DCHECK_LE(start_index + match_length, static_cast<int32_t>(string.size())); |
| 56 |
| 57 if (string_start_index) |
| 58 *string_start_index = static_cast<size_t>(start_index); |
| 59 if (string_match_length) |
| 60 *string_match_length = static_cast<size_t>(match_length); |
| 61 return true; |
| 62 } |
| 63 |
| 64 // Fills |classifications| based on |match_spans|, which consists of sorted |
| 65 // [index, length] pairs for matching text. Overlapping or adjacent spans are |
| 66 // collapsed where possible. Returns false on invalid input. |
| 67 bool FillACMatchClassifications( |
| 68 const ContactProvider::MatchSpans& match_spans, |
| 69 size_t text_length, |
| 70 AutocompleteMatch::ACMatchClassifications* classifications) { |
| 71 DCHECK(classifications); |
| 72 classifications->clear(); |
| 73 |
| 74 // Index where the previous span started. |
| 75 size_t last_span_start = 0; |
| 76 |
| 77 // One beyond the final character in the previous span (e.g. if there's a |
| 78 // span at offset 0 with length 1, this will be set to 1). |
| 79 size_t last_span_end = 0; |
| 80 |
| 81 for (ContactProvider::MatchSpans::const_iterator it = match_spans.begin(); |
| 82 it != match_spans.end(); ++it) { |
| 83 size_t span_start = it->first; |
| 84 size_t span_length = it->second; |
| 85 |
| 86 if (span_start < last_span_start || span_start + span_length > text_length) |
| 87 return false; |
| 88 |
| 89 if (span_length == 0) |
| 90 continue; |
| 91 |
| 92 if (span_start > last_span_end || last_span_end == 0) { |
| 93 if (span_start != 0) { |
| 94 // If we're not at the beginning of the string, add a preceding unstyled |
| 95 // classification. |
| 96 classifications->push_back( |
| 97 ACMatchClassification( |
| 98 last_span_end, |
| 99 AutocompleteMatch::ACMatchClassification::NONE)); |
| 100 } |
| 101 classifications->push_back( |
| 102 ACMatchClassification( |
| 103 span_start, AutocompleteMatch::ACMatchClassification::MATCH)); |
| 104 last_span_end = span_start + span_length; |
| 105 } else { |
| 106 // This span starts at or before the point where the last span ended, so |
| 107 // don't bother adding an additional classification. |
| 108 last_span_end = std::max(last_span_end, span_start + span_length); |
| 109 } |
| 110 |
| 111 last_span_start = span_start; |
| 112 } |
| 113 |
| 114 if (last_span_end < text_length) { |
| 115 classifications->push_back( |
| 116 ACMatchClassification( |
| 117 last_span_end, AutocompleteMatch::ACMatchClassification::NONE)); |
| 118 } |
| 119 |
| 120 return true; |
| 121 } |
| 122 |
| 123 } // namespace |
| 124 |
| 125 // static |
| 126 const char ContactProvider::kMatchContactIdKey[] = "contact_id"; |
| 127 |
| 128 // Cached information about a contact. |
| 129 struct ContactProvider::ContactData { |
| 130 ContactData(const string16& full_name, |
| 131 const string16& given_name, |
| 132 const string16& family_name, |
| 133 const std::string& contact_id) |
| 134 : full_name(full_name), |
| 135 given_name(given_name), |
| 136 family_name(family_name), |
| 137 given_name_index(0), |
| 138 family_name_index(0), |
| 139 given_name_index_valid(false), |
| 140 family_name_index_valid(false), |
| 141 contact_id(contact_id) { |
| 142 given_name_index_valid = |
| 143 FindFirstSubstring(full_name, given_name, &given_name_index, NULL); |
| 144 family_name_index_valid = |
| 145 FindFirstSubstring(full_name, family_name, &family_name_index, NULL); |
| 146 } |
| 147 |
| 148 string16 full_name; |
| 149 string16 given_name; |
| 150 string16 family_name; |
| 151 |
| 152 // Indices into |full_name| where |given_name| and |family_name| first appear. |
| 153 // Only valid when the corresponding |*_index_valid| field is true. |
| 154 size_t given_name_index; |
| 155 size_t family_name_index; |
| 156 |
| 157 // Do |given_name| or |family_name| appear in |full_name|? |
| 158 bool given_name_index_valid; |
| 159 bool family_name_index_valid; |
| 160 |
| 161 // Unique ID used to look up additional contact information. |
| 162 std::string contact_id; |
| 163 }; |
| 164 |
| 165 ContactProvider::TestAPI::TestAPI(ContactProvider* provider) |
| 166 : provider_(provider) { |
| 167 } |
| 168 |
| 169 ContactProvider::TestAPI::~TestAPI() { |
| 170 provider_ = NULL; |
| 171 } |
| 172 |
| 173 // static |
| 174 AutocompleteMatch::ACMatchClassifications |
| 175 ContactProvider::TestAPI::RunFillACMatchClassifications( |
| 176 const MatchSpans& match_spans, |
| 177 size_t text_length) { |
| 178 ACMatchClassifications classifications; |
| 179 if (FillACMatchClassifications(match_spans, text_length, &classifications)) |
| 180 return classifications; |
| 181 else |
| 182 return ACMatchClassifications(); |
| 183 } |
| 184 |
| 185 ContactProvider::ContactProvider( |
| 186 AutocompleteProviderListener* listener, |
| 187 Profile* profile, |
| 188 base::WeakPtr<contacts::ContactManagerInterface> contact_manager) |
| 189 : AutocompleteProvider(listener, profile, "Contacts"), |
| 190 contact_manager_(contact_manager) { |
| 191 contact_manager_->AddObserver(this, profile); |
| 192 RefreshContacts(); |
| 193 } |
| 194 |
| 195 void ContactProvider::Start(const AutocompleteInput& input, |
| 196 bool minimal_changes) { |
| 197 matches_.clear(); |
| 198 |
| 199 if (input.type() != AutocompleteInput::UNKNOWN && |
| 200 input.type() != AutocompleteInput::QUERY && |
| 201 input.type() != AutocompleteInput::FORCED_QUERY) { |
| 202 return; |
| 203 } |
| 204 |
| 205 std::vector<string16> input_parts; |
| 206 base::SplitStringAlongWhitespace(input.text(), &input_parts); |
| 207 |
| 208 for (ContactDataVector::const_iterator it = contacts_.begin(); |
| 209 it != contacts_.end(); ++it) { |
| 210 const ContactData& contact = *it; |
| 211 |
| 212 // [index, length] spans within |contact.full_name| that are matched by the |
| 213 // input. |
| 214 MatchSpans match_spans; |
| 215 |
| 216 bool is_matched = false; |
| 217 size_t match_index = 0, match_length = 0; |
| 218 |
| 219 // First, check if the whole input string is a prefix of the full name. |
| 220 if (FindFirstSubstring( |
| 221 contact.full_name, input.text(), &match_index, &match_length) && |
| 222 match_index == 0) { |
| 223 is_matched = true; |
| 224 match_spans.push_back(std::make_pair(0, match_length)); |
| 225 } else { |
| 226 // If not, check whether every search term is a prefix of the given name |
| 227 // or the family name. |
| 228 for (std::vector<string16>::const_iterator it = input_parts.begin(); |
| 229 it != input_parts.end(); ++it) { |
| 230 if (FindFirstSubstring( |
| 231 contact.given_name, *it, &match_index, &match_length) && |
| 232 match_index == 0) { |
| 233 is_matched = true; |
| 234 if (contact.given_name_index_valid) { |
| 235 match_spans.push_back( |
| 236 std::make_pair(contact.given_name_index, match_length)); |
| 237 } |
| 238 } else if ( |
| 239 FindFirstSubstring( |
| 240 contact.family_name, *it, &match_index, &match_length) && |
| 241 match_index == 0) { |
| 242 is_matched = true; |
| 243 if (contact.family_name_index_valid) { |
| 244 match_spans.push_back( |
| 245 std::make_pair(contact.family_name_index, match_length)); |
| 246 } |
| 247 } else { |
| 248 is_matched = false; |
| 249 break; |
| 250 } |
| 251 } |
| 252 } |
| 253 |
| 254 if (is_matched) { |
| 255 std::sort(match_spans.begin(), match_spans.end()); |
| 256 matches_.push_back(CreateAutocompleteMatch(input, contact, match_spans)); |
| 257 } |
| 258 } |
| 259 } |
| 260 |
| 261 void ContactProvider::OnContactsUpdated(Profile* profile) { |
| 262 DCHECK_EQ(profile, profile_); |
| 263 RefreshContacts(); |
| 264 } |
| 265 |
| 266 ContactProvider::~ContactProvider() { |
| 267 // Like ContactProvider, ContactManager gets destroyed at profile destruction. |
| 268 // Make sure that this class doesn't try to access ContactManager after |
| 269 // ContactManager is gone. |
| 270 if (contact_manager_.get()) |
| 271 contact_manager_->RemoveObserver(this, profile_); |
| 272 } |
| 273 |
| 274 void ContactProvider::RefreshContacts() { |
| 275 typedef std::vector<const contacts::Contact*> ContactPointers; |
| 276 scoped_ptr<ContactPointers> contacts = |
| 277 contact_manager_->GetAllContacts(profile_); |
| 278 |
| 279 contacts_.clear(); |
| 280 for (ContactPointers::const_iterator it = contacts->begin(); |
| 281 it != contacts->end(); ++it) { |
| 282 const contacts::Contact& contact = **it; |
| 283 string16 full_name = |
| 284 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.full_name())); |
| 285 string16 given_name = |
| 286 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.given_name())); |
| 287 string16 family_name = |
| 288 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.family_name())); |
| 289 |
| 290 if (!full_name.empty()) { |
| 291 contacts_.push_back( |
| 292 ContactData( |
| 293 full_name, given_name, family_name, contact.contact_id())); |
| 294 } |
| 295 } |
| 296 } |
| 297 |
| 298 AutocompleteMatch ContactProvider::CreateAutocompleteMatch( |
| 299 const AutocompleteInput& input, |
| 300 const ContactData& contact, |
| 301 const MatchSpans& match_spans) { |
| 302 AutocompleteMatch match(this, 0, false, AutocompleteMatch::CONTACT); |
| 303 match.fill_into_edit = input.text(); |
| 304 match.inline_autocomplete_offset = string16::npos; |
| 305 match.contents = contact.full_name; |
| 306 match.relevance = 1000; // FIXME: update autocomplete_provider.h |
| 307 match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id); |
| 308 FillACMatchClassifications( |
| 309 match_spans, |
| 310 match.contents.length(), |
| 311 &match.contents_class); |
| 312 return match; |
| 313 } |
OLD | NEW |