Remove wpr.archive_info dependancy on page to avoid circular dependancies.
[chromium-blink-merge.git] / ui / app_list / search / mixer.cc
blobb66365c4df223997af3146cf90d0b8a1f4bbdb8b
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.
5 #include "ui/app_list/search/mixer.h"
7 #include <algorithm>
8 #include <map>
9 #include <set>
10 #include <string>
11 #include <vector>
13 #include "ui/app_list/search_provider.h"
14 #include "ui/app_list/search_result.h"
16 namespace app_list {
18 namespace {
20 // Maximum number of results to show.
21 const size_t kMaxResults = 6;
23 // A value to indicate no max number of results limit.
24 const size_t kNoMaxResultsLimit = 0;
26 void UpdateResult(const SearchResult& source, SearchResult* target) {
27 target->set_display_type(source.display_type());
28 target->set_title(source.title());
29 target->set_title_tags(source.title_tags());
30 target->set_details(source.details());
31 target->set_details_tags(source.details_tags());
34 } // namespace
36 Mixer::SortData::SortData() : result(NULL), score(0.0) {
39 Mixer::SortData::SortData(SearchResult* result, double score)
40 : result(result), score(score) {
43 bool Mixer::SortData::operator<(const SortData& other) const {
44 // This data precedes (less than) |other| if it has higher score.
45 return score > other.score;
48 // Used to group relevant providers together fox mixing their results.
49 class Mixer::Group {
50 public:
51 Group(size_t max_results, double boost)
52 : max_results_(max_results), boost_(boost) {}
53 ~Group() {}
55 void AddProvider(SearchProvider* provider) { providers_.push_back(provider); }
57 void FetchResults(bool is_voice_query, const KnownResults& known_results) {
58 results_.clear();
60 for (const SearchProvider* provider : providers_) {
61 for (SearchResult* result : provider->results()) {
62 DCHECK(!result->id().empty());
64 // We cannot rely on providers to give relevance scores in the range
65 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the
66 // Google+ API). Clamp to that range.
67 double relevance = std::min(std::max(result->relevance(), 0.0), 1.0);
69 double boost = boost_;
70 KnownResults::const_iterator known_it =
71 known_results.find(result->id());
72 if (known_it != known_results.end()) {
73 switch (known_it->second) {
74 case PERFECT_PRIMARY:
75 boost = 4.0;
76 break;
77 case PREFIX_PRIMARY:
78 boost = 3.75;
79 break;
80 case PERFECT_SECONDARY:
81 boost = 3.25;
82 break;
83 case PREFIX_SECONDARY:
84 boost = 3.0;
85 break;
86 case UNKNOWN_RESULT:
87 NOTREACHED() << "Unknown result in KnownResults?";
88 break;
92 // If this is a voice query, voice results receive a massive boost.
93 if (is_voice_query && result->voice_result())
94 boost += 4.0;
96 results_.push_back(SortData(result, relevance + boost));
100 std::sort(results_.begin(), results_.end());
101 if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_)
102 results_.resize(max_results_);
105 const SortedResults& results() const { return results_; }
107 private:
108 typedef std::vector<SearchProvider*> Providers;
109 const size_t max_results_;
110 const double boost_;
112 Providers providers_; // Not owned.
113 SortedResults results_;
115 DISALLOW_COPY_AND_ASSIGN(Group);
118 Mixer::Mixer(AppListModel::SearchResults* ui_results)
119 : ui_results_(ui_results) {
121 Mixer::~Mixer() {
124 size_t Mixer::AddGroup(size_t max_results, double boost) {
125 groups_.push_back(new Group(max_results, boost));
126 return groups_.size() - 1;
129 size_t Mixer::AddOmniboxGroup(size_t max_results, double boost) {
130 // There should not already be an omnibox group.
131 DCHECK(!has_omnibox_group_);
132 size_t id = AddGroup(max_results, boost);
133 omnibox_group_ = id;
134 has_omnibox_group_ = true;
135 return id;
138 void Mixer::AddProviderToGroup(size_t group_id, SearchProvider* provider) {
139 groups_[group_id]->AddProvider(provider);
142 void Mixer::MixAndPublish(bool is_voice_query,
143 const KnownResults& known_results) {
144 FetchResults(is_voice_query, known_results);
146 SortedResults results;
147 results.reserve(kMaxResults);
149 // Add results from non-omnibox groups first.
150 for (size_t i = 0; i < groups_.size(); ++i) {
151 if (!has_omnibox_group_ || i != omnibox_group_) {
152 const Group& group = *groups_[i];
153 results.insert(results.end(), group.results().begin(),
154 group.results().end());
158 // Collapse duplicate apps from local and web store.
159 RemoveDuplicates(&results);
161 // Fill the remaining slots with omnibox results. Always add at least one
162 // omnibox result (even if there are no more slots; if we over-fill the
163 // vector, the web store and people results will be removed in a later step).
164 if (has_omnibox_group_) {
165 CHECK_LT(omnibox_group_, groups_.size());
166 const Group& omnibox_group = *groups_[omnibox_group_];
167 const size_t omnibox_results = std::min(
168 omnibox_group.results().size(),
169 results.size() < kMaxResults ? kMaxResults - results.size() : 1);
170 results.insert(results.end(), omnibox_group.results().begin(),
171 omnibox_group.results().begin() + omnibox_results);
174 std::sort(results.begin(), results.end());
175 RemoveDuplicates(&results);
176 if (results.size() > kMaxResults)
177 results.resize(kMaxResults);
179 Publish(results, ui_results_);
182 void Mixer::Publish(const SortedResults& new_results,
183 AppListModel::SearchResults* ui_results) {
184 typedef std::map<std::string, SearchResult*> IdToResultMap;
186 // The following algorithm is used:
187 // 1. Transform the |ui_results| list into an unordered map from result ID
188 // to item.
189 // 2. Use the order of items in |new_results| to build an ordered list. If the
190 // result IDs exist in the map, update and use the item in the map and delete
191 // it from the map afterwards. Otherwise, clone new items from |new_results|.
192 // 3. Delete the objects remaining in the map as they are unused.
194 // A map of the items in |ui_results| that takes ownership of the items.
195 IdToResultMap ui_results_map;
196 for (SearchResult* ui_result : *ui_results)
197 ui_results_map[ui_result->id()] = ui_result;
198 // We have to erase all results at once so that observers are notified with
199 // meaningful indexes.
200 ui_results->RemoveAll();
202 // Add items back to |ui_results| in the order of |new_results|.
203 for (const SortData& sort_data : new_results) {
204 const SearchResult& new_result = *sort_data.result;
205 IdToResultMap::const_iterator ui_result_it =
206 ui_results_map.find(new_result.id());
207 if (ui_result_it != ui_results_map.end()) {
208 // Update and use the old result if it exists.
209 SearchResult* ui_result = ui_result_it->second;
210 UpdateResult(new_result, ui_result);
211 ui_result->set_relevance(sort_data.score);
213 // |ui_results| takes back ownership from |ui_results_map| here.
214 ui_results->Add(ui_result);
216 // Remove the item from the map so that it ends up only with unused
217 // results.
218 ui_results_map.erase(ui_result->id());
219 } else {
220 scoped_ptr<SearchResult> result_copy = new_result.Duplicate();
221 result_copy->set_relevance(sort_data.score);
222 // Copy the result from |new_results| otherwise.
223 ui_results->Add(result_copy.release());
227 // Delete the results remaining in the map as they are not in the new results.
228 for (const auto& ui_result : ui_results_map) {
229 delete ui_result.second;
233 void Mixer::RemoveDuplicates(SortedResults* results) {
234 SortedResults final;
235 final.reserve(results->size());
237 std::set<std::string> id_set;
238 for (const SortData& sort_data : *results) {
239 const std::string& id = sort_data.result->id();
240 if (id_set.find(id) != id_set.end())
241 continue;
243 id_set.insert(id);
244 final.push_back(sort_data);
247 results->swap(final);
250 void Mixer::FetchResults(bool is_voice_query,
251 const KnownResults& known_results) {
252 for (auto* group : groups_)
253 group->FetchResults(is_voice_query, known_results);
256 } // namespace app_list