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"
13 #include "ui/app_list/search_provider.h"
14 #include "ui/app_list/search_result.h"
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 const size_t kMaxSuggestionsResults
= 6;
27 // A value to indicate no max number of results limit.
28 const size_t kNoMaxResultsLimit
= 0;
30 void UpdateResult(const SearchResult
& source
, SearchResult
* target
) {
31 target
->set_display_type(source
.display_type());
32 target
->set_title(source
.title());
33 target
->set_title_tags(source
.title_tags());
34 target
->set_details(source
.details());
35 target
->set_details_tags(source
.details_tags());
40 Mixer::SortData::SortData() : result(NULL
), score(0.0) {
43 Mixer::SortData::SortData(SearchResult
* result
, double score
)
44 : result(result
), score(score
) {
47 bool Mixer::SortData::operator<(const SortData
& other
) const {
48 // This data precedes (less than) |other| if it has higher score.
49 return score
> other
.score
;
52 // Used to group relevant providers together fox mixing their results.
55 Group(size_t max_results
, double boost
)
56 : max_results_(max_results
), boost_(boost
) {}
59 void AddProvider(SearchProvider
* provider
) { providers_
.push_back(provider
); }
61 void FetchResults(bool is_voice_query
, const KnownResults
& known_results
) {
64 for (const SearchProvider
* provider
: providers_
) {
65 for (SearchResult
* result
: provider
->results()) {
66 DCHECK(!result
->id().empty());
68 // We cannot rely on providers to give relevance scores in the range
69 // [0.0, 1.0] (e.g., PeopleProvider directly gives values from the
70 // Google+ API). Clamp to that range.
71 double relevance
= std::min(std::max(result
->relevance(), 0.0), 1.0);
73 double boost
= boost_
;
74 KnownResults::const_iterator known_it
=
75 known_results
.find(result
->id());
76 if (known_it
!= known_results
.end()) {
77 switch (known_it
->second
) {
84 case PERFECT_SECONDARY
:
87 case PREFIX_SECONDARY
:
91 NOTREACHED() << "Unknown result in KnownResults?";
96 // If this is a voice query, voice results receive a massive boost.
97 if (is_voice_query
&& result
->voice_result())
100 results_
.push_back(SortData(result
, relevance
+ boost
));
104 std::sort(results_
.begin(), results_
.end());
105 if (max_results_
!= kNoMaxResultsLimit
&& results_
.size() > max_results_
)
106 results_
.resize(max_results_
);
109 const SortedResults
& results() const { return results_
; }
112 typedef std::vector
<SearchProvider
*> Providers
;
113 const size_t max_results_
;
116 Providers providers_
; // Not owned.
117 SortedResults results_
;
119 DISALLOW_COPY_AND_ASSIGN(Group
);
122 Mixer::Mixer(AppListModel::SearchResults
* ui_results
)
123 : ui_results_(ui_results
) {
129 groups_
[MAIN_GROUP
].reset(new Group(kMaxMainGroupResults
, 3.0));
130 groups_
[OMNIBOX_GROUP
].reset(new Group(kNoMaxResultsLimit
, 2.0));
131 groups_
[WEBSTORE_GROUP
].reset(new Group(kMaxWebstoreResults
, 1.0));
132 groups_
[PEOPLE_GROUP
].reset(new Group(kMaxPeopleResults
, 0.0));
133 groups_
[SUGGESTIONS_GROUP
].reset(new Group(kMaxSuggestionsResults
, 3.0));
136 void Mixer::AddProviderToGroup(GroupId group
, SearchProvider
* provider
) {
137 groups_
[group
]->AddProvider(provider
);
140 void Mixer::MixAndPublish(bool is_voice_query
,
141 const KnownResults
& known_results
) {
142 FetchResults(is_voice_query
, known_results
);
144 SortedResults results
;
145 results
.reserve(kMaxResults
);
147 const Group
& main_group
= *groups_
[MAIN_GROUP
];
148 const Group
& omnibox_group
= *groups_
[OMNIBOX_GROUP
];
149 const Group
& webstore_group
= *groups_
[WEBSTORE_GROUP
];
150 const Group
& people_group
= *groups_
[PEOPLE_GROUP
];
151 const Group
& suggestions_group
= *groups_
[SUGGESTIONS_GROUP
];
153 // Adds main group and web store results first.
154 results
.insert(results
.end(), main_group
.results().begin(),
155 main_group
.results().end());
156 results
.insert(results
.end(), webstore_group
.results().begin(),
157 webstore_group
.results().end());
158 results
.insert(results
.end(), people_group
.results().begin(),
159 people_group
.results().end());
160 results
.insert(results
.end(), suggestions_group
.results().begin(),
161 suggestions_group
.results().end());
163 // Collapse duplicate apps from local and web store.
164 RemoveDuplicates(&results
);
166 // Fill the remaining slots with omnibox results. Always add at least one
167 // omnibox result (even if there are no more slots; if we over-fill the
168 // vector, the web store and people results will be removed in a later step).
169 const size_t omnibox_results
=
170 std::min(omnibox_group
.results().size(),
171 results
.size() < kMaxResults
? kMaxResults
- results
.size() : 1);
172 results
.insert(results
.end(), omnibox_group
.results().begin(),
173 omnibox_group
.results().begin() + omnibox_results
);
175 std::sort(results
.begin(), results
.end());
176 RemoveDuplicates(&results
);
177 if (results
.size() > kMaxResults
)
178 results
.resize(kMaxResults
);
180 Publish(results
, ui_results_
);
183 void Mixer::Publish(const SortedResults
& new_results
,
184 AppListModel::SearchResults
* ui_results
) {
185 typedef std::map
<std::string
, SearchResult
*> IdToResultMap
;
187 // The following algorithm is used:
188 // 1. Transform the |ui_results| list into an unordered map from result ID
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.
195 // A map of the items in |ui_results| that takes ownership of the items.
196 IdToResultMap ui_results_map
;
197 for (SearchResult
* ui_result
: *ui_results
)
198 ui_results_map
[ui_result
->id()] = ui_result
;
199 // We have to erase all results at once so that observers are notified with
200 // meaningful indexes.
201 ui_results
->RemoveAll();
203 // Add items back to |ui_results| in the order of |new_results|.
204 for (const SortData
& sort_data
: new_results
) {
205 const SearchResult
& new_result
= *sort_data
.result
;
206 IdToResultMap::const_iterator ui_result_it
=
207 ui_results_map
.find(new_result
.id());
208 if (ui_result_it
!= ui_results_map
.end()) {
209 // Update and use the old result if it exists.
210 SearchResult
* ui_result
= ui_result_it
->second
;
211 UpdateResult(new_result
, ui_result
);
212 ui_result
->set_relevance(sort_data
.score
);
214 // |ui_results| takes back ownership from |ui_results_map| here.
215 ui_results
->Add(ui_result
);
217 // Remove the item from the map so that it ends up only with unused
219 ui_results_map
.erase(ui_result
->id());
221 scoped_ptr
<SearchResult
> result_copy
= new_result
.Duplicate();
222 result_copy
->set_relevance(sort_data
.score
);
223 // Copy the result from |new_results| otherwise.
224 ui_results
->Add(result_copy
.release());
228 // Delete the results remaining in the map as they are not in the new results.
229 for (const auto& ui_result
: ui_results_map
) {
230 delete ui_result
.second
;
234 void Mixer::RemoveDuplicates(SortedResults
* results
) {
236 final
.reserve(results
->size());
238 std::set
<std::string
> id_set
;
239 for (const SortData
& sort_data
: *results
) {
240 const std::string
& id
= sort_data
.result
->id();
241 if (id_set
.find(id
) != id_set
.end())
245 final
.push_back(sort_data
);
248 results
->swap(final
);
251 void Mixer::FetchResults(bool is_voice_query
,
252 const KnownResults
& known_results
) {
253 for (const auto& item
: groups_
)
254 item
.second
->FetchResults(is_voice_query
, known_results
);
257 } // namespace app_list