app_result.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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/app_result.h"
6
7#include "base/time/time.h"
8#include "chrome/browser/extensions/extension_service.h"
9#include "chrome/browser/extensions/extension_util.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/app_list/app_context_menu.h"
12#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
13#include "chrome/browser/ui/app_list/search/tokenized_string.h"
14#include "chrome/browser/ui/app_list/search/tokenized_string_match.h"
15#include "chrome/browser/ui/extensions/extension_enable_flow.h"
16#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
17#include "content/public/browser/user_metrics.h"
18#include "extensions/browser/extension_registry.h"
19#include "extensions/browser/extension_system.h"
20#include "extensions/browser/extension_system_provider.h"
21#include "extensions/browser/extensions_browser_client.h"
22#include "extensions/common/constants.h"
23#include "extensions/common/extension.h"
24#include "extensions/common/extension_icon_set.h"
25#include "extensions/common/manifest_handlers/icons_handler.h"
26#include "ui/app_list/app_list_switches.h"
27#include "ui/gfx/color_utils.h"
28#include "ui/gfx/image/image_skia_operations.h"
29
30namespace app_list {
31
32AppResult::AppResult(Profile* profile,
33                     const std::string& app_id,
34                     AppListControllerDelegate* controller)
35    : profile_(profile),
36      app_id_(app_id),
37      controller_(controller),
38      extension_registry_(NULL) {
39  set_id(extensions::Extension::GetBaseURLFromExtensionId(app_id_).spec());
40  if (app_list::switches::IsExperimentalAppListEnabled())
41    set_display_type(DISPLAY_TILE);
42
43  const extensions::Extension* extension =
44      extensions::ExtensionSystem::Get(profile_)->extension_service()
45          ->GetInstalledExtension(app_id_);
46  DCHECK(extension);
47
48  is_platform_app_ = extension->is_platform_app();
49
50  icon_.reset(
51      new extensions::IconImage(profile_,
52                                extension,
53                                extensions::IconsInfo::GetIcons(extension),
54                                GetPreferredIconDimension(),
55                                extensions::util::GetDefaultAppIcon(),
56                                this));
57  UpdateIcon();
58
59  StartObservingExtensionRegistry();
60}
61
62AppResult::~AppResult() {
63  StopObservingExtensionRegistry();
64}
65
66void AppResult::UpdateFromMatch(const TokenizedString& title,
67                                const TokenizedStringMatch& match) {
68  const TokenizedStringMatch::Hits& hits = match.hits();
69
70  Tags tags;
71  tags.reserve(hits.size());
72  for (size_t i = 0; i < hits.size(); ++i)
73    tags.push_back(Tag(Tag::MATCH, hits[i].start(), hits[i].end()));
74
75  set_title(title.text());
76  set_title_tags(tags);
77  set_relevance(match.relevance());
78}
79
80void AppResult::UpdateFromLastLaunched(const base::Time& current_time,
81                                       const base::Time& last_launched) {
82  base::TimeDelta delta = current_time - last_launched;
83  DCHECK_LE(0, delta.InSeconds());
84  const int kSecondsInWeek = 60 * 60 * 24 * 7;
85
86  // Set the relevance to a value between 0 and 1. This function decays as the
87  // time delta increases and reaches a value of 0.5 at 1 week.
88  set_relevance(1 / (1 + delta.InSecondsF() / kSecondsInWeek));
89}
90
91void AppResult::Open(int event_flags) {
92  const extensions::Extension* extension =
93      extensions::ExtensionSystem::Get(profile_)->extension_service()
94          ->GetInstalledExtension(app_id_);
95  if (!extension)
96    return;
97
98  // Don't auto-enable apps that cannot be launched.
99  if (!extensions::util::IsAppLaunchable(app_id_, profile_))
100    return;
101
102  // Check if enable flow is already running or should be started
103  if (RunExtensionEnableFlow())
104    return;
105
106  CoreAppLauncherHandler::RecordAppListSearchLaunch(extension);
107  content::RecordAction(
108      base::UserMetricsAction("AppList_ClickOnAppFromSearch"));
109
110  controller_->ActivateApp(
111      profile_,
112      extension,
113      AppListControllerDelegate::LAUNCH_FROM_APP_LIST_SEARCH,
114      event_flags);
115}
116
117void AppResult::InvokeAction(int action_index, int event_flags) {}
118
119scoped_ptr<ChromeSearchResult> AppResult::Duplicate() {
120  scoped_ptr<ChromeSearchResult> copy(
121      new AppResult(profile_, app_id_, controller_));
122  copy->set_title(title());
123  copy->set_title_tags(title_tags());
124
125  return copy.Pass();
126}
127
128ChromeSearchResultType AppResult::GetType() {
129  return APP_SEARCH_RESULT;
130}
131
132ui::MenuModel* AppResult::GetContextMenuModel() {
133  if (!context_menu_) {
134    context_menu_.reset(new AppContextMenu(
135        this, profile_, app_id_, controller_));
136    context_menu_->set_is_platform_app(is_platform_app_);
137    context_menu_->set_is_search_result(true);
138  }
139
140  return context_menu_->GetMenuModel();
141}
142
143void AppResult::StartObservingExtensionRegistry() {
144  DCHECK(!extension_registry_);
145
146  extension_registry_ = extensions::ExtensionRegistry::Get(profile_);
147  extension_registry_->AddObserver(this);
148}
149
150void AppResult::StopObservingExtensionRegistry() {
151  if (extension_registry_)
152    extension_registry_->RemoveObserver(this);
153  extension_registry_ = NULL;
154}
155
156bool AppResult::RunExtensionEnableFlow() {
157  if (extensions::util::IsAppLaunchableWithoutEnabling(app_id_, profile_))
158    return false;
159
160  if (!extension_enable_flow_) {
161    controller_->OnShowChildDialog();
162
163    extension_enable_flow_.reset(new ExtensionEnableFlow(
164        profile_, app_id_, this));
165    extension_enable_flow_->StartForNativeWindow(
166        controller_->GetAppListWindow());
167  }
168  return true;
169}
170
171void AppResult::UpdateIcon() {
172  gfx::ImageSkia icon = icon_->image_skia();
173
174  if (!extensions::util::IsAppLaunchable(app_id_, profile_)) {
175    const color_utils::HSL shift = {-1, 0, 0.6};
176    icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
177  }
178
179  SetIcon(icon);
180}
181
182void AppResult::OnExtensionIconImageChanged(extensions::IconImage* image) {
183  DCHECK_EQ(icon_.get(), image);
184  UpdateIcon();
185}
186
187void AppResult::ExecuteLaunchCommand(int event_flags) {
188  Open(event_flags);
189}
190
191void AppResult::ExtensionEnableFlowFinished() {
192  extension_enable_flow_.reset();
193  controller_->OnCloseChildDialog();
194
195  // Automatically open app after enabling.
196  Open(ui::EF_NONE);
197}
198
199void AppResult::ExtensionEnableFlowAborted(bool user_initiated) {
200  extension_enable_flow_.reset();
201  controller_->OnCloseChildDialog();
202}
203
204void AppResult::OnExtensionLoaded(content::BrowserContext* browser_context,
205                                  const extensions::Extension* extension) {
206  UpdateIcon();
207}
208
209void AppResult::OnExtensionUninstalled(content::BrowserContext* browser_context,
210                                       const extensions::Extension* extension,
211                                       extensions::UninstallReason reason) {
212  if (extension->id() != app_id_)
213    return;
214
215  NotifyItemUninstalled();
216}
217
218void AppResult::OnShutdown(extensions::ExtensionRegistry* registry) {
219  DCHECK_EQ(extension_registry_, registry);
220  StopObservingExtensionRegistry();
221}
222
223}  // namespace app_list
224