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 "chrome/browser/ui/app_list/search/app_search_provider.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/clock.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_ui_util.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
18 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
19 #include "chrome/browser/ui/app_list/search/app_result.h"
20 #include "extensions/browser/extension_prefs.h"
21 #include "extensions/browser/extension_registry.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/extension_set.h"
25 #include "ui/app_list/app_list_item.h"
26 #include "ui/app_list/app_list_model.h"
27 #include "ui/app_list/search/tokenized_string.h"
28 #include "ui/app_list/search/tokenized_string_match.h"
30 using extensions::ExtensionRegistry
;
34 // The size of each step unlaunched apps should increase their relevance by.
35 const double kUnlaunchedAppRelevanceStepSize
= 0.0001;
40 class AppSearchProvider::App
{
42 explicit App(const extensions::Extension
* extension
,
43 const base::Time
& last_launch_time
)
44 : app_id_(extension
->id()),
45 indexed_name_(base::UTF8ToUTF16(extension
->short_name())),
46 last_launch_time_(last_launch_time
) {}
49 const std::string
& app_id() const { return app_id_
; }
50 const TokenizedString
& indexed_name() const { return indexed_name_
; }
51 const base::Time
& last_launch_time() const { return last_launch_time_
; }
54 const std::string app_id_
;
55 TokenizedString indexed_name_
;
56 base::Time last_launch_time_
;
58 DISALLOW_COPY_AND_ASSIGN(App
);
61 AppSearchProvider::AppSearchProvider(Profile
* profile
,
62 AppListControllerDelegate
* list_controller
,
63 scoped_ptr
<base::Clock
> clock
,
64 AppListItemList
* top_level_item_list
)
66 list_controller_(list_controller
),
67 extension_registry_observer_(this),
68 top_level_item_list_(top_level_item_list
),
70 update_results_factory_(this) {
71 extension_registry_observer_
.Add(ExtensionRegistry::Get(profile_
));
75 AppSearchProvider::~AppSearchProvider() {}
77 void AppSearchProvider::Start(bool /*is_voice_query*/,
78 const base::string16
& query
) {
80 const TokenizedString
query_terms(query
);
84 bool show_recommendations
= query
.empty();
85 // Refresh list of apps to ensure we have the latest launch time information.
86 // This will also cause the results to update.
87 if (show_recommendations
)
93 void AppSearchProvider::Stop() {
96 void AppSearchProvider::UpdateResults() {
97 const TokenizedString
query_terms(query_
);
98 bool show_recommendations
= query_
.empty();
101 if (show_recommendations
) {
102 // Build a map of app ids to their position in the app list.
103 std::map
<std::string
, size_t> id_to_app_list_index
;
104 for (size_t i
= 0; i
< top_level_item_list_
->item_count(); ++i
) {
105 id_to_app_list_index
[top_level_item_list_
->item_at(i
)->id()] = i
;
108 for (const App
* app
: apps_
) {
109 scoped_ptr
<AppResult
> result(
110 new AppResult(profile_
, app
->app_id(), list_controller_
, true));
111 result
->set_title(app
->indexed_name().text());
113 // Use the app list order to tiebreak apps that have never been launched.
114 if (app
->last_launch_time().is_null()) {
115 auto it
= id_to_app_list_index
.find(app
->app_id());
116 // If it's in a folder, it won't be in |id_to_app_list_index|. Rank
117 // those as if they are at the end of the list.
118 size_t app_list_index
=
119 it
== id_to_app_list_index
.end() ? apps_
.size() : (*it
).second
;
120 if (app_list_index
> apps_
.size())
121 app_list_index
= apps_
.size();
123 result
->set_relevance(kUnlaunchedAppRelevanceStepSize
*
124 (apps_
.size() - app_list_index
));
126 result
->UpdateFromLastLaunched(clock_
->Now(), app
->last_launch_time());
131 for (const App
* app
: apps_
) {
132 scoped_ptr
<AppResult
> result(
133 new AppResult(profile_
, app
->app_id(), list_controller_
, false));
134 TokenizedStringMatch match
;
135 if (!match
.Calculate(query_terms
, app
->indexed_name()))
138 result
->UpdateFromMatch(app
->indexed_name(), match
);
143 update_results_factory_
.InvalidateWeakPtrs();
146 void AppSearchProvider::AddApps(const extensions::ExtensionSet
& extensions
) {
147 extensions::ExtensionPrefs
* prefs
= extensions::ExtensionPrefs::Get(profile_
);
148 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
149 iter
!= extensions
.end(); ++iter
) {
150 const extensions::Extension
* app
= iter
->get();
152 if (!extensions::ui_util::ShouldDisplayInAppLauncher(app
, profile_
))
155 if (profile_
->IsOffTheRecord() &&
156 !extensions::util::CanLoadInIncognito(app
, profile_
))
159 apps_
.push_back(new App(app
, prefs
->GetLastLaunchTime(app
->id())));
163 void AppSearchProvider::RefreshApps() {
165 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
166 AddApps(registry
->enabled_extensions());
167 AddApps(registry
->disabled_extensions());
168 AddApps(registry
->terminated_extensions());
171 void AppSearchProvider::OnExtensionLoaded(
172 content::BrowserContext
* browser_context
,
173 const extensions::Extension
* extension
) {
175 if (!update_results_factory_
.HasWeakPtrs()) {
176 base::MessageLoop::current()->PostTask(
178 base::Bind(&AppSearchProvider::UpdateResults
,
179 update_results_factory_
.GetWeakPtr()));
183 void AppSearchProvider::OnExtensionUninstalled(
184 content::BrowserContext
* browser_context
,
185 const extensions::Extension
* extension
,
186 extensions::UninstallReason reason
) {
188 if (!update_results_factory_
.HasWeakPtrs()) {
189 base::MessageLoop::current()->PostTask(
191 base::Bind(&AppSearchProvider::UpdateResults
,
192 update_results_factory_
.GetWeakPtr()));
196 } // namespace app_list