1 // Copyright 2013 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/ui/app_list/search/mixer.h" 6 7 #include <algorithm> 8 #include <map> 9 #include <set> 10 #include <string> 11 #include <vector> 12 13 #include "chrome/browser/ui/app_list/search/chrome_search_result.h" 14 #include "ui/app_list/search_provider.h" 15 16 namespace app_list { 17 18 namespace { 19 20 // Maximum number of results to show. 21 const size_t kMaxResults = 6; 22 const size_t kMaxMainGroupResults = 4; 23 const size_t kMaxWebstoreResults = 2; 24 const size_t kMaxPeopleResults = 2; 25 26 // A value to indicate no max number of results limit. 27 const size_t kNoMaxResultsLimit = 0; 28 29 void UpdateResult(const ChromeSearchResult& source, 30 ChromeSearchResult* target) { 31 target->set_title(source.title()); 32 target->set_title_tags(source.title_tags()); 33 target->set_details(source.details()); 34 target->set_details_tags(source.details_tags()); 35 } 36 37 } // namespace 38 39 Mixer::SortData::SortData() : result(NULL), score(0.0) { 40 } 41 42 Mixer::SortData::SortData(ChromeSearchResult* result, double score) 43 : result(result), score(score) { 44 } 45 46 bool Mixer::SortData::operator<(const SortData& other) const { 47 // This data precedes (less than) |other| if it has higher score. 48 return score > other.score; 49 } 50 51 // Used to group relevant providers together fox mixing their results. 52 class Mixer::Group { 53 public: 54 Group(size_t max_results, double boost) 55 : max_results_(max_results), 56 boost_(boost) { 57 } 58 ~Group() {} 59 60 void AddProvider(SearchProvider* provider) { 61 providers_.push_back(provider); 62 } 63 64 void FetchResults(const KnownResults& known_results) { 65 results_.clear(); 66 67 for (Providers::const_iterator provider_it = providers_.begin(); 68 provider_it != providers_.end(); 69 ++provider_it) { 70 for (SearchProvider::Results::const_iterator 71 result_it = (*provider_it)->results().begin(); 72 result_it != (*provider_it)->results().end(); 73 ++result_it) { 74 DCHECK_GE((*result_it)->relevance(), 0.0); 75 DCHECK_LE((*result_it)->relevance(), 1.0); 76 DCHECK(!(*result_it)->id().empty()); 77 78 double boost = boost_; 79 KnownResults::const_iterator known_it = 80 known_results.find((*result_it)->id()); 81 if (known_it != known_results.end()) { 82 switch (known_it->second) { 83 case PERFECT_PRIMARY: 84 boost = 4.0; 85 break; 86 case PREFIX_PRIMARY: 87 boost = 3.75; 88 break; 89 case PERFECT_SECONDARY: 90 boost = 3.25; 91 break; 92 case PREFIX_SECONDARY: 93 boost = 3.0; 94 break; 95 case UNKNOWN_RESULT: 96 NOTREACHED() << "Unknown result in KnownResults?"; 97 break; 98 } 99 } 100 101 results_.push_back( 102 SortData(static_cast<ChromeSearchResult*>(*result_it), 103 (*result_it)->relevance() + boost)); 104 } 105 } 106 107 std::sort(results_.begin(), results_.end()); 108 if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_) 109 results_.resize(max_results_); 110 } 111 112 const SortedResults& results() const { return results_; } 113 114 private: 115 typedef std::vector<SearchProvider*> Providers; 116 const size_t max_results_; 117 const double boost_; 118 119 Providers providers_; // Not owned. 120 SortedResults results_; 121 122 DISALLOW_COPY_AND_ASSIGN(Group); 123 }; 124 125 Mixer::Mixer(AppListModel::SearchResults* ui_results) 126 : ui_results_(ui_results) {} 127 Mixer::~Mixer() {} 128 129 void Mixer::Init() { 130 groups_.push_back(new Group(kMaxMainGroupResults, 3.0)); 131 groups_.push_back(new Group(kNoMaxResultsLimit, 2.0)); 132 groups_.push_back(new Group(kMaxWebstoreResults, 1.0)); 133 groups_.push_back(new Group(kMaxPeopleResults, 0.0)); 134 } 135 136 void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) { 137 size_t group_index = static_cast<size_t>(group); 138 groups_[group_index]->AddProvider(provider); 139 } 140 141 void Mixer::MixAndPublish(const KnownResults& known_results) { 142 FetchResults(known_results); 143 144 SortedResults results; 145 results.reserve(kMaxResults); 146 147 // Adds main group and web store results first. 148 results.insert(results.end(), 149 groups_[MAIN_GROUP]->results().begin(), 150 groups_[MAIN_GROUP]->results().end()); 151 results.insert(results.end(), 152 groups_[WEBSTORE_GROUP]->results().begin(), 153 groups_[WEBSTORE_GROUP]->results().end()); 154 results.insert(results.end(), 155 groups_[PEOPLE_GROUP]->results().begin(), 156 groups_[PEOPLE_GROUP]->results().end()); 157 158 // Collapse duplicate apps from local and web store. 159 RemoveDuplicates(&results); 160 161 DCHECK_GE(kMaxResults, results.size()); 162 size_t remaining_slots = kMaxResults - results.size(); 163 164 // Reserves at least one slot for the omnibox result. If there is no available 165 // slot for omnibox results, removes the last one from web store. 166 const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size(); 167 if (!remaining_slots && omnibox_results) 168 results.pop_back(); 169 170 remaining_slots = std::min(kMaxResults - results.size(), omnibox_results); 171 results.insert(results.end(), 172 groups_[OMNIBOX_GROUP]->results().begin(), 173 groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots); 174 175 std::sort(results.begin(), results.end()); 176 RemoveDuplicates(&results); 177 if (results.size() > kMaxResults) 178 results.resize(kMaxResults); 179 180 Publish(results, ui_results_); 181 } 182 183 void Mixer::Publish(const SortedResults& new_results, 184 AppListModel::SearchResults* ui_results) { 185 typedef std::map<std::string, ChromeSearchResult*> IdToResultMap; 186 187 // The following algorithm is used: 188 // 1. Transform the |ui_results| list into an unordered map from result ID 189 // to item. 190 // 2. Use the order of items in |new_results| to build an ordered list. If the 191 // result IDs exist in the map, update and use the item in the map and delete 192 // it from the map afterwards. Otherwise, clone new items from |new_results|. 193 // 3. Delete the objects remaining in the map as they are unused. 194 195 // A map of the items in |ui_results| that takes ownership of the items. 196 IdToResultMap ui_results_map; 197 for (size_t i = 0; i < ui_results->item_count(); ++i) { 198 ChromeSearchResult* ui_result = 199 static_cast<ChromeSearchResult*>(ui_results->GetItemAt(i)); 200 ui_results_map[ui_result->id()] = ui_result; 201 } 202 // We have to erase all results at once so that observers are notified with 203 // meaningful indexes. 204 ui_results->RemoveAll(); 205 206 // Add items back to |ui_results| in the order of |new_results|. 207 for (size_t i = 0; i < new_results.size(); ++i) { 208 ChromeSearchResult* new_result = new_results[i].result; 209 IdToResultMap::const_iterator ui_result_it = 210 ui_results_map.find(new_result->id()); 211 if (ui_result_it != ui_results_map.end()) { 212 // Update and use the old result if it exists. 213 ChromeSearchResult* ui_result = ui_result_it->second; 214 UpdateResult(*new_result, ui_result); 215 216 // |ui_results| takes back ownership from |ui_results_map| here. 217 ui_results->Add(ui_result); 218 219 // Remove the item from the map so that it ends up only with unused 220 // results. 221 ui_results_map.erase(ui_result->id()); 222 } else { 223 // Copy the result from |new_results| otherwise. 224 ui_results->Add(new_result->Duplicate().release()); 225 } 226 } 227 228 // Delete the results remaining in the map as they are not in the new results. 229 for (IdToResultMap::const_iterator ui_result_it = ui_results_map.begin(); 230 ui_result_it != ui_results_map.end(); 231 ++ui_result_it) { 232 delete ui_result_it->second; 233 } 234 } 235 236 void Mixer::RemoveDuplicates(SortedResults* results) { 237 SortedResults final; 238 final.reserve(results->size()); 239 240 std::set<std::string> id_set; 241 for (SortedResults::iterator it = results->begin(); it != results->end(); 242 ++it) { 243 const std::string& id = it->result->id(); 244 if (id_set.find(id) != id_set.end()) 245 continue; 246 247 id_set.insert(id); 248 final.push_back(*it); 249 } 250 251 results->swap(final); 252 } 253 254 void Mixer::FetchResults(const KnownResults& known_results) { 255 for (Groups::iterator group_it = groups_.begin(); 256 group_it != groups_.end(); 257 ++group_it) { 258 (*group_it)->FetchResults(known_results); 259 } 260 } 261 262 } // namespace app_list 263