Home | History | Annotate | Download | only in autocomplete
      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 #include <cmath>
      9 
     10 #include "base/i18n/break_iterator.h"
     11 #include "base/i18n/string_search.h"
     12 #include "base/strings/string16.h"
     13 #include "base/strings/string_split.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "chrome/browser/autocomplete/autocomplete_input.h"
     16 #include "chrome/browser/chromeos/contacts/contact.pb.h"
     17 #include "chrome/browser/chromeos/contacts/contact_manager.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 
     20 namespace {
     21 
     22 // Default affinity assigned to contacts whose |affinity| field is unset.
     23 // TODO(derat): Set this to something reasonable (probably 0.0) once we're
     24 // getting affinity for contacts.
     25 float kDefaultAffinity = 1.0;
     26 
     27 // Base match relevance assigned to a contact with an affinity of 0.0.
     28 int kBaseRelevance = 1300;
     29 
     30 // Maximum boost to relevance for a contact with an affinity of 1.0.
     31 int kAffinityRelevanceBoost = 200;
     32 
     33 // Returns true if |word_to_find| is a prefix of |name_to_search| and marks the
     34 // matching text in |classifications| (which corresponds to the contact's full
     35 // name).  |name_index_in_full_name| contains |name_to_search|'s index within
     36 // the full name or string16::npos if it doesn't appear in it.
     37 bool WordIsNamePrefix(const string16& word_to_find,
     38                       const string16& name_to_search,
     39                       size_t name_index_in_full_name,
     40                       size_t full_name_length,
     41                       ACMatchClassifications* classifications) {
     42   DCHECK(classifications);
     43 
     44   size_t match_index = 0;
     45   size_t match_length = 0;
     46   if (!base::i18n::StringSearchIgnoringCaseAndAccents(word_to_find,
     47       name_to_search, &match_index, &match_length) || (match_index != 0))
     48     return false;
     49 
     50   if (name_index_in_full_name != string16::npos) {
     51     AutocompleteMatch::ACMatchClassifications new_class;
     52     AutocompleteMatch::ClassifyLocationInString(name_index_in_full_name,
     53         match_length, full_name_length, 0, &new_class);
     54     *classifications = AutocompleteMatch::MergeClassifications(
     55         *classifications, new_class);
     56   }
     57 
     58   return true;
     59 }
     60 
     61 }  // namespace
     62 
     63 // static
     64 const char ContactProvider::kMatchContactIdKey[] = "contact_id";
     65 
     66 // Cached information about a contact.
     67 struct ContactProvider::ContactData {
     68   ContactData(const string16& full_name,
     69               const string16& given_name,
     70               const string16& family_name,
     71               const std::string& contact_id,
     72               float affinity)
     73       : full_name(full_name),
     74         given_name(given_name),
     75         family_name(family_name),
     76         given_name_index(string16::npos),
     77         family_name_index(string16::npos),
     78         contact_id(contact_id),
     79         affinity(affinity) {
     80     base::i18n::StringSearchIgnoringCaseAndAccents(
     81         given_name, full_name, &given_name_index, NULL);
     82     base::i18n::StringSearchIgnoringCaseAndAccents(
     83         family_name, full_name, &family_name_index, NULL);
     84   }
     85 
     86   string16 full_name;
     87   string16 given_name;
     88   string16 family_name;
     89 
     90   // Indices into |full_name| where |given_name| and |family_name| first appear,
     91   // or string16::npos if they don't appear in it.
     92   size_t given_name_index;
     93   size_t family_name_index;
     94 
     95   // Unique ID used to look up additional contact information.
     96   std::string contact_id;
     97 
     98   // Affinity between the user and this contact, in the range [0.0, 1.0].
     99   float affinity;
    100 };
    101 
    102 ContactProvider::ContactProvider(
    103     AutocompleteProviderListener* listener,
    104     Profile* profile,
    105     base::WeakPtr<contacts::ContactManagerInterface> contact_manager)
    106     : AutocompleteProvider(listener, profile, TYPE_CONTACT),
    107       contact_manager_(contact_manager) {
    108   contact_manager_->AddObserver(this, profile);
    109   RefreshContacts();
    110 }
    111 
    112 void ContactProvider::Start(const AutocompleteInput& input,
    113                             bool minimal_changes) {
    114   if (minimal_changes)
    115     return;
    116 
    117   matches_.clear();
    118 
    119   if (input.type() != AutocompleteInput::UNKNOWN &&
    120       input.type() != AutocompleteInput::QUERY &&
    121       input.type() != AutocompleteInput::FORCED_QUERY)
    122     return;
    123 
    124   std::vector<string16> input_words;
    125   base::i18n::BreakIterator break_iterator(
    126       input.text(),
    127       base::i18n::BreakIterator::BREAK_WORD);
    128   if (break_iterator.Init()) {
    129     while (break_iterator.Advance()) {
    130       if (break_iterator.IsWord())
    131         input_words.push_back(break_iterator.GetString());
    132     }
    133   }
    134 
    135   // |contacts_| is ordered by descending affinity.  Since affinity is currently
    136   // the only signal used for computing relevance, we can stop after we've found
    137   // kMaxMatches results.
    138   for (ContactDataVector::const_iterator it = contacts_.begin();
    139        it != contacts_.end() && matches_.size() < kMaxMatches; ++it)
    140     AddContactIfMatched(input, input_words, *it);
    141 }
    142 
    143 void ContactProvider::OnContactsUpdated(Profile* profile) {
    144   DCHECK_EQ(profile, profile_);
    145   RefreshContacts();
    146 }
    147 
    148 ContactProvider::~ContactProvider() {
    149   // Like ContactProvider, ContactManager gets destroyed at profile destruction.
    150   // Make sure that this class doesn't try to access ContactManager after
    151   // ContactManager is gone.
    152   if (contact_manager_.get())
    153     contact_manager_->RemoveObserver(this, profile_);
    154 }
    155 
    156 // static
    157 bool ContactProvider::CompareAffinity(const ContactData& a,
    158                                       const ContactData& b) {
    159   return a.affinity > b.affinity;
    160 }
    161 
    162 void ContactProvider::RefreshContacts() {
    163   if (!contact_manager_.get())
    164     return;
    165 
    166   scoped_ptr<contacts::ContactPointers> contacts =
    167       contact_manager_->GetAllContacts(profile_);
    168 
    169   contacts_.clear();
    170   contacts_.reserve(contacts->size());
    171   for (contacts::ContactPointers::const_iterator it = contacts->begin();
    172        it != contacts->end(); ++it) {
    173     const contacts::Contact& contact = **it;
    174     string16 full_name =
    175         AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.full_name()));
    176     string16 given_name =
    177         AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.given_name()));
    178     string16 family_name =
    179         AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.family_name()));
    180     float affinity =
    181         contact.has_affinity() ? contact.affinity() : kDefaultAffinity;
    182 
    183     if (!full_name.empty()) {
    184       contacts_.push_back(
    185           ContactData(full_name, given_name, family_name, contact.contact_id(),
    186                       affinity));
    187     }
    188   }
    189   std::sort(contacts_.begin(), contacts_.end(), CompareAffinity);
    190 }
    191 
    192 void ContactProvider::AddContactIfMatched(
    193     const AutocompleteInput& input,
    194     const std::vector<string16>& input_words,
    195     const ContactData& contact) {
    196   // First, check if the whole input string is a prefix of the full name.
    197   // TODO(derat): Consider additionally segmenting the full name so we can match
    198   // e.g. middle names or initials even when they aren't typed as a prefix of
    199   // the full name.
    200   ACMatchClassifications classifications;
    201   if (!WordIsNamePrefix(input.text(), contact.full_name, 0,
    202                         contact.full_name.size(), &classifications)) {
    203     // If not, check whether every search term is a prefix of the given name
    204     // or the family name.
    205     if (input_words.empty())
    206       return;
    207 
    208     // TODO(derat): Check new matches against previous ones to make sure they
    209     // don't overlap (e.g. the query "bob b" against a contact with full name
    210     // "Bob G. Bryson", given name "Bob", and family name "Bryson" should result
    211     // in classifications "_Bob_ G. _B_ryson" rather than "_Bob_ G. Bryson".
    212     for (std::vector<string16>::const_iterator it = input_words.begin();
    213          it != input_words.end(); ++it) {
    214       if (!WordIsNamePrefix(*it, contact.given_name, contact.given_name_index,
    215                             contact.full_name.size(), &classifications) &&
    216           !WordIsNamePrefix(*it, contact.family_name, contact.family_name_index,
    217                             contact.full_name.size(), &classifications))
    218         return;
    219     }
    220   }
    221 
    222   matches_.push_back(CreateAutocompleteMatch(input, contact));
    223   matches_.back().contents_class = classifications;
    224 }
    225 
    226 AutocompleteMatch ContactProvider::CreateAutocompleteMatch(
    227     const AutocompleteInput& input,
    228     const ContactData& contact) {
    229   AutocompleteMatch match(this, 0, false, AutocompleteMatchType::CONTACT);
    230   match.contents = contact.full_name;
    231   match.fill_into_edit = match.contents;
    232   match.relevance = kBaseRelevance +
    233       static_cast<int>(roundf(kAffinityRelevanceBoost * contact.affinity));
    234   match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id);
    235   return match;
    236 }
    237