blob: 9fd5c76705c00b8c34b6a620140d413866319ae2 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/app_search_provider.h"
#include <string>
#include "base/bind.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/clock.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ui/app_list/search/app_result.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_model.h"
#include "ui/app_list/search/tokenized_string.h"
#include "ui/app_list/search/tokenized_string_match.h"
using extensions::ExtensionRegistry;
namespace {
// The size of each step unlaunched apps should increase their relevance by.
const double kUnlaunchedAppRelevanceStepSize = 0.0001;
}
namespace app_list {
class AppSearchProvider::App {
public:
explicit App(const extensions::Extension* extension,
const base::Time& last_launch_time)
: app_id_(extension->id()),
indexed_name_(base::UTF8ToUTF16(extension->short_name())),
last_launch_time_(last_launch_time) {}
~App() {}
const std::string& app_id() const { return app_id_; }
const TokenizedString& indexed_name() const { return indexed_name_; }
const base::Time& last_launch_time() const { return last_launch_time_; }
private:
const std::string app_id_;
TokenizedString indexed_name_;
base::Time last_launch_time_;
DISALLOW_COPY_AND_ASSIGN(App);
};
AppSearchProvider::AppSearchProvider(Profile* profile,
AppListControllerDelegate* list_controller,
scoped_ptr<base::Clock> clock,
AppListItemList* top_level_item_list)
: profile_(profile),
list_controller_(list_controller),
extension_registry_observer_(this),
top_level_item_list_(top_level_item_list),
clock_(clock.Pass()),
update_results_factory_(this) {
extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
RefreshApps();
}
AppSearchProvider::~AppSearchProvider() {}
void AppSearchProvider::Start(bool /*is_voice_query*/,
const base::string16& query) {
query_ = query;
const TokenizedString query_terms(query);
ClearResults();
bool show_recommendations = query.empty();
// Refresh list of apps to ensure we have the latest launch time information.
// This will also cause the results to update.
if (show_recommendations)
RefreshApps();
UpdateResults();
}
void AppSearchProvider::Stop() {
}
void AppSearchProvider::UpdateResults() {
const TokenizedString query_terms(query_);
bool show_recommendations = query_.empty();
ClearResults();
if (show_recommendations) {
// Build a map of app ids to their position in the app list.
std::map<std::string, size_t> id_to_app_list_index;
for (size_t i = 0; i < top_level_item_list_->item_count(); ++i) {
id_to_app_list_index[top_level_item_list_->item_at(i)->id()] = i;
}
for (const App* app : apps_) {
scoped_ptr<AppResult> result(
new AppResult(profile_, app->app_id(), list_controller_, true));
result->set_title(app->indexed_name().text());
// Use the app list order to tiebreak apps that have never been launched.
if (app->last_launch_time().is_null()) {
auto it = id_to_app_list_index.find(app->app_id());
// If it's in a folder, it won't be in |id_to_app_list_index|. Rank
// those as if they are at the end of the list.
size_t app_list_index =
it == id_to_app_list_index.end() ? apps_.size() : (*it).second;
if (app_list_index > apps_.size())
app_list_index = apps_.size();
result->set_relevance(kUnlaunchedAppRelevanceStepSize *
(apps_.size() - app_list_index));
} else {
result->UpdateFromLastLaunched(clock_->Now(), app->last_launch_time());
}
Add(result.Pass());
}
} else {
for (const App* app : apps_) {
scoped_ptr<AppResult> result(
new AppResult(profile_, app->app_id(), list_controller_, false));
TokenizedStringMatch match;
if (!match.Calculate(query_terms, app->indexed_name()))
continue;
result->UpdateFromMatch(app->indexed_name(), match);
Add(result.Pass());
}
}
update_results_factory_.InvalidateWeakPtrs();
}
void AppSearchProvider::AddApps(const extensions::ExtensionSet& extensions) {
extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
iter != extensions.end(); ++iter) {
const extensions::Extension* app = iter->get();
if (!extensions::ui_util::ShouldDisplayInAppLauncher(app, profile_))
continue;
if (profile_->IsOffTheRecord() &&
!extensions::util::CanLoadInIncognito(app, profile_))
continue;
apps_.push_back(new App(app, prefs->GetLastLaunchTime(app->id())));
}
}
void AppSearchProvider::RefreshApps() {
apps_.clear();
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
AddApps(registry->enabled_extensions());
AddApps(registry->disabled_extensions());
AddApps(registry->terminated_extensions());
}
void AppSearchProvider::OnExtensionLoaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension) {
RefreshApps();
if (!update_results_factory_.HasWeakPtrs()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&AppSearchProvider::UpdateResults,
update_results_factory_.GetWeakPtr()));
}
}
void AppSearchProvider::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UninstallReason reason) {
RefreshApps();
// This should not be batched as the UI needs to immediately be informed of
// deleted extensions to prevent use-after-frees.
UpdateResults();
}
} // namespace app_list