Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/autocomplete/contact_provider_chromeos.h" | 5 #include "chrome/browser/autocomplete/contact_provider_chromeos.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <cmath> | |
| 8 | 9 |
| 9 #include "base/i18n/break_iterator.h" | 10 #include "base/i18n/break_iterator.h" |
| 10 #include "base/i18n/string_search.h" | 11 #include "base/i18n/string_search.h" |
| 11 #include "base/string_split.h" | 12 #include "base/string_split.h" |
| 12 #include "base/string16.h" | 13 #include "base/string16.h" |
| 13 #include "base/utf_string_conversions.h" | 14 #include "base/utf_string_conversions.h" |
| 14 #include "chrome/browser/autocomplete/autocomplete_input.h" | 15 #include "chrome/browser/autocomplete/autocomplete_input.h" |
| 15 #include "chrome/browser/chromeos/contacts/contact.pb.h" | 16 #include "chrome/browser/chromeos/contacts/contact.pb.h" |
| 16 #include "chrome/browser/chromeos/contacts/contact_manager.h" | 17 #include "chrome/browser/chromeos/contacts/contact_manager.h" |
| 17 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
| 18 | 19 |
| 19 namespace { | 20 namespace { |
| 20 | 21 |
| 22 // Default affinity assigned to contacts whose |affinity| field is unset. | |
| 23 float kDefaultAffinity = 1.0; | |
|
Peter Kasting
2012/09/13 23:32:37
Isn't this kind of high? I guess I don't totally
Daniel Erat
2012/09/14 15:31:50
Dirty secret: We're not getting affinity data yet,
| |
| 24 | |
| 21 // Returns true if |word_to_find| is a prefix of |name_to_search| and marks the | 25 // Returns true if |word_to_find| is a prefix of |name_to_search| and marks the |
| 22 // matching text in |classifications| (which corresponds to the contact's full | 26 // matching text in |classifications| (which corresponds to the contact's full |
| 23 // name). |name_index_in_full_name| contains |name_to_search|'s index within | 27 // name). |name_index_in_full_name| contains |name_to_search|'s index within |
| 24 // the full name or string16::npos if it doesn't appear in it. | 28 // the full name or string16::npos if it doesn't appear in it. |
| 25 bool WordIsNamePrefix(const string16& word_to_find, | 29 bool WordIsNamePrefix(const string16& word_to_find, |
| 26 const string16& name_to_search, | 30 const string16& name_to_search, |
| 27 size_t name_index_in_full_name, | 31 size_t name_index_in_full_name, |
| 28 size_t full_name_length, | 32 size_t full_name_length, |
| 29 ACMatchClassifications* classifications) { | 33 ACMatchClassifications* classifications) { |
| 30 DCHECK(classifications); | 34 DCHECK(classifications); |
| 31 | 35 |
| 32 size_t match_index = 0; | 36 size_t match_index = 0; |
| 33 size_t match_length = 0; | 37 size_t match_length = 0; |
| 34 if (!base::i18n::StringSearchIgnoringCaseAndAccents(word_to_find, | 38 if (!base::i18n::StringSearchIgnoringCaseAndAccents(word_to_find, |
| 35 name_to_search, &match_index, &match_length) || (match_index != 0)) | 39 name_to_search, &match_index, &match_length) || (match_index != 0)) |
| 36 return false; | 40 return false; |
| 37 | 41 |
| 38 if (name_index_in_full_name != string16::npos) { | 42 if (name_index_in_full_name != string16::npos) { |
| 39 AutocompleteMatch::ACMatchClassifications new_class; | 43 AutocompleteMatch::ACMatchClassifications new_class; |
| 40 AutocompleteMatch::ClassifyLocationInString(name_index_in_full_name, | 44 AutocompleteMatch::ClassifyLocationInString(name_index_in_full_name, |
| 41 match_length, full_name_length, 0, &new_class); | 45 match_length, full_name_length, 0, &new_class); |
| 42 *classifications = AutocompleteMatch::MergeClassifications( | 46 *classifications = AutocompleteMatch::MergeClassifications( |
| 43 *classifications, new_class); | 47 *classifications, new_class); |
| 44 } | 48 } |
| 45 | 49 |
| 46 return true; | 50 return true; |
| 47 } | 51 } |
| 48 | 52 |
| 53 // Returns true if |a|'s affinity is greater than |b|'s affinity. | |
| 54 bool CompareContactAffinity(const contacts::Contact* a, | |
| 55 const contacts::Contact* b) { | |
| 56 float affinity_a = a->has_affinity() ? a->affinity() : kDefaultAffinity; | |
|
Peter Kasting
2012/09/13 23:32:37
Super tiny nit: Seems like |a_affinity| would be j
Daniel Erat
2012/09/14 15:31:50
Done.
| |
| 57 float affinity_b = b->has_affinity() ? b->affinity() : kDefaultAffinity; | |
| 58 return affinity_a > affinity_b; | |
| 59 } | |
| 60 | |
| 49 } // namespace | 61 } // namespace |
| 50 | 62 |
| 51 // static | 63 // static |
| 52 const char ContactProvider::kMatchContactIdKey[] = "contact_id"; | 64 const char ContactProvider::kMatchContactIdKey[] = "contact_id"; |
| 65 const int ContactProvider::kBaseRelevance = 1300; | |
|
Daniel Erat
2012/09/13 23:10:08
I just pulled numbers out of the air for this. Si
| |
| 66 const int ContactProvider::kAffinityRelevanceBoost = 200; | |
| 53 | 67 |
| 54 // Cached information about a contact. | 68 // Cached information about a contact. |
| 55 struct ContactProvider::ContactData { | 69 struct ContactProvider::ContactData { |
| 56 ContactData(const string16& full_name, | 70 ContactData(const string16& full_name, |
| 57 const string16& given_name, | 71 const string16& given_name, |
| 58 const string16& family_name, | 72 const string16& family_name, |
| 59 const std::string& contact_id) | 73 const std::string& contact_id, |
| 74 float affinity) | |
| 60 : full_name(full_name), | 75 : full_name(full_name), |
| 61 given_name(given_name), | 76 given_name(given_name), |
| 62 family_name(family_name), | 77 family_name(family_name), |
| 63 given_name_index(string16::npos), | 78 given_name_index(string16::npos), |
| 64 family_name_index(string16::npos), | 79 family_name_index(string16::npos), |
| 65 contact_id(contact_id) { | 80 contact_id(contact_id), |
| 81 affinity(affinity) { | |
| 66 base::i18n::StringSearchIgnoringCaseAndAccents( | 82 base::i18n::StringSearchIgnoringCaseAndAccents( |
| 67 given_name, full_name, &given_name_index, NULL); | 83 given_name, full_name, &given_name_index, NULL); |
| 68 base::i18n::StringSearchIgnoringCaseAndAccents( | 84 base::i18n::StringSearchIgnoringCaseAndAccents( |
| 69 family_name, full_name, &family_name_index, NULL); | 85 family_name, full_name, &family_name_index, NULL); |
| 70 } | 86 } |
| 71 | 87 |
| 72 string16 full_name; | 88 string16 full_name; |
| 73 string16 given_name; | 89 string16 given_name; |
| 74 string16 family_name; | 90 string16 family_name; |
| 75 | 91 |
| 76 // Indices into |full_name| where |given_name| and |family_name| first appear, | 92 // Indices into |full_name| where |given_name| and |family_name| first appear, |
| 77 // or string16::npos if they don't appear in it. | 93 // or string16::npos if they don't appear in it. |
| 78 size_t given_name_index; | 94 size_t given_name_index; |
| 79 size_t family_name_index; | 95 size_t family_name_index; |
| 80 | 96 |
| 81 // Unique ID used to look up additional contact information. | 97 // Unique ID used to look up additional contact information. |
| 82 std::string contact_id; | 98 std::string contact_id; |
| 99 | |
| 100 // Affinity between the user and this contact, in the range [0.0, 1.0]. | |
| 101 float affinity; | |
| 83 }; | 102 }; |
| 84 | 103 |
| 85 ContactProvider::ContactProvider( | 104 ContactProvider::ContactProvider( |
| 86 AutocompleteProviderListener* listener, | 105 AutocompleteProviderListener* listener, |
| 87 Profile* profile, | 106 Profile* profile, |
| 88 base::WeakPtr<contacts::ContactManagerInterface> contact_manager) | 107 base::WeakPtr<contacts::ContactManagerInterface> contact_manager) |
| 89 : AutocompleteProvider(listener, profile, TYPE_CONTACT), | 108 : AutocompleteProvider(listener, profile, TYPE_CONTACT), |
| 90 contact_manager_(contact_manager) { | 109 contact_manager_(contact_manager) { |
| 91 contact_manager_->AddObserver(this, profile); | 110 contact_manager_->AddObserver(this, profile); |
| 92 RefreshContacts(); | 111 RefreshContacts(); |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 108 base::i18n::BreakIterator break_iterator( | 127 base::i18n::BreakIterator break_iterator( |
| 109 input.text(), | 128 input.text(), |
| 110 base::i18n::BreakIterator::BREAK_WORD); | 129 base::i18n::BreakIterator::BREAK_WORD); |
| 111 if (break_iterator.Init()) { | 130 if (break_iterator.Init()) { |
| 112 while (break_iterator.Advance()) { | 131 while (break_iterator.Advance()) { |
| 113 if (break_iterator.IsWord()) | 132 if (break_iterator.IsWord()) |
| 114 input_words.push_back(break_iterator.GetString()); | 133 input_words.push_back(break_iterator.GetString()); |
| 115 } | 134 } |
| 116 } | 135 } |
| 117 | 136 |
| 137 // |contacts_| is ordered by descending affinity. Since affinity is currently | |
| 138 // the only signal used for computing relevance, we can stop after we've found | |
| 139 // kMaxMatches results. | |
| 118 for (ContactDataVector::const_iterator it = contacts_.begin(); | 140 for (ContactDataVector::const_iterator it = contacts_.begin(); |
| 119 it != contacts_.end(); ++it) | 141 it != contacts_.end() && matches_.size() < kMaxMatches; ++it) |
| 120 AddContactIfMatched(input, input_words, *it); | 142 AddContactIfMatched(input, input_words, *it); |
| 121 } | 143 } |
| 122 | 144 |
| 123 void ContactProvider::OnContactsUpdated(Profile* profile) { | 145 void ContactProvider::OnContactsUpdated(Profile* profile) { |
| 124 DCHECK_EQ(profile, profile_); | 146 DCHECK_EQ(profile, profile_); |
| 125 RefreshContacts(); | 147 RefreshContacts(); |
| 126 } | 148 } |
| 127 | 149 |
| 128 ContactProvider::~ContactProvider() { | 150 ContactProvider::~ContactProvider() { |
| 129 // Like ContactProvider, ContactManager gets destroyed at profile destruction. | 151 // Like ContactProvider, ContactManager gets destroyed at profile destruction. |
| 130 // Make sure that this class doesn't try to access ContactManager after | 152 // Make sure that this class doesn't try to access ContactManager after |
| 131 // ContactManager is gone. | 153 // ContactManager is gone. |
| 132 if (contact_manager_.get()) | 154 if (contact_manager_.get()) |
| 133 contact_manager_->RemoveObserver(this, profile_); | 155 contact_manager_->RemoveObserver(this, profile_); |
| 134 } | 156 } |
| 135 | 157 |
| 136 void ContactProvider::RefreshContacts() { | 158 void ContactProvider::RefreshContacts() { |
| 137 if (!contact_manager_.get()) | 159 if (!contact_manager_.get()) |
| 138 return; | 160 return; |
| 139 | 161 |
| 140 scoped_ptr<contacts::ContactPointers> contacts = | 162 scoped_ptr<contacts::ContactPointers> contacts = |
| 141 contact_manager_->GetAllContacts(profile_); | 163 contact_manager_->GetAllContacts(profile_); |
| 164 std::sort(contacts->begin(), contacts->end(), CompareContactAffinity); | |
|
Peter Kasting
2012/09/13 23:32:37
Nit: If we move this down to the bottom of the fun
Daniel Erat
2012/09/14 15:31:50
I went back and forth on this. Sorting pointers s
| |
| 142 | 165 |
| 143 contacts_.clear(); | 166 contacts_.clear(); |
| 144 for (contacts::ContactPointers::const_iterator it = contacts->begin(); | 167 for (contacts::ContactPointers::const_iterator it = contacts->begin(); |
| 145 it != contacts->end(); ++it) { | 168 it != contacts->end(); ++it) { |
| 146 const contacts::Contact& contact = **it; | 169 const contacts::Contact& contact = **it; |
| 147 string16 full_name = | 170 string16 full_name = |
| 148 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.full_name())); | 171 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.full_name())); |
| 149 string16 given_name = | 172 string16 given_name = |
| 150 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.given_name())); | 173 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.given_name())); |
| 151 string16 family_name = | 174 string16 family_name = |
| 152 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.family_name())); | 175 AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.family_name())); |
| 176 float affinity = | |
| 177 contact.has_affinity() ? contact.affinity() : kDefaultAffinity; | |
| 153 | 178 |
| 154 if (!full_name.empty()) { | 179 if (!full_name.empty()) { |
| 155 contacts_.push_back( | 180 contacts_.push_back( |
| 156 ContactData( | 181 ContactData(full_name, given_name, family_name, contact.contact_id(), |
| 157 full_name, given_name, family_name, contact.contact_id())); | 182 affinity)); |
| 158 } | 183 } |
| 159 } | 184 } |
| 160 } | 185 } |
| 161 | 186 |
| 162 void ContactProvider::AddContactIfMatched( | 187 void ContactProvider::AddContactIfMatched( |
| 163 const AutocompleteInput& input, | 188 const AutocompleteInput& input, |
| 164 const std::vector<string16>& input_words, | 189 const std::vector<string16>& input_words, |
| 165 const ContactData& contact) { | 190 const ContactData& contact) { |
| 166 // First, check if the whole input string is a prefix of the full name. | 191 // First, check if the whole input string is a prefix of the full name. |
| 167 // TODO(derat): Consider additionally segmenting the full name so we can match | 192 // TODO(derat): Consider additionally segmenting the full name so we can match |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 193 matches_.back().contents_class = classifications; | 218 matches_.back().contents_class = classifications; |
| 194 } | 219 } |
| 195 | 220 |
| 196 AutocompleteMatch ContactProvider::CreateAutocompleteMatch( | 221 AutocompleteMatch ContactProvider::CreateAutocompleteMatch( |
| 197 const AutocompleteInput& input, | 222 const AutocompleteInput& input, |
| 198 const ContactData& contact) { | 223 const ContactData& contact) { |
| 199 AutocompleteMatch match(this, 0, false, AutocompleteMatch::CONTACT); | 224 AutocompleteMatch match(this, 0, false, AutocompleteMatch::CONTACT); |
| 200 match.inline_autocomplete_offset = string16::npos; | 225 match.inline_autocomplete_offset = string16::npos; |
| 201 match.contents = contact.full_name; | 226 match.contents = contact.full_name; |
| 202 match.fill_into_edit = match.contents; | 227 match.fill_into_edit = match.contents; |
| 203 // TODO(derat): Implement ranking. | 228 match.relevance = kBaseRelevance + |
| 204 match.relevance = 1; | 229 static_cast<int>(roundf(kAffinityRelevanceBoost * contact.affinity)); |
| 205 match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id); | 230 match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id); |
| 206 return match; | 231 return match; |
| 207 } | 232 } |
| OLD | NEW |