webstore_result.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/webstore/webstore_result.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/memory/ref_counted.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/apps/ephemeral_app_launcher.h"
13#include "chrome/browser/extensions/extension_service.h"
14#include "chrome/browser/extensions/install_tracker.h"
15#include "chrome/browser/extensions/install_tracker_factory.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
18#include "chrome/browser/ui/app_list/search/common/url_icon_source.h"
19#include "chrome/browser/ui/app_list/search/webstore/webstore_installer.h"
20#include "chrome/browser/ui/browser_navigator.h"
21#include "chrome/browser/ui/extensions/application_launch.h"
22#include "chrome/grit/chromium_strings.h"
23#include "chrome/grit/generated_resources.h"
24#include "extensions/browser/extension_registry.h"
25#include "extensions/browser/extension_system.h"
26#include "extensions/browser/extension_util.h"
27#include "extensions/common/extension.h"
28#include "extensions/common/extension_urls.h"
29#include "grit/theme_resources.h"
30#include "net/base/url_util.h"
31#include "ui/base/l10n/l10n_util.h"
32#include "ui/base/resource/resource_bundle.h"
33#include "ui/gfx/canvas.h"
34#include "ui/gfx/image/canvas_image_source.h"
35
36namespace {
37
38const int kLaunchEphemeralAppAction = 1;
39
40// BadgedImageSource adds a webstore badge to a webstore app icon.
41class BadgedIconSource : public gfx::CanvasImageSource {
42 public:
43  BadgedIconSource(const gfx::ImageSkia& icon, const gfx::Size& icon_size)
44      : CanvasImageSource(icon_size, false), icon_(icon) {}
45
46  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
47    canvas->DrawImageInt(icon_, 0, 0);
48    const gfx::ImageSkia& badge = *ui::ResourceBundle::GetSharedInstance().
49         GetImageSkiaNamed(IDR_WEBSTORE_ICON_16);
50    canvas->DrawImageInt(
51        badge, icon_.width() - badge.width(), icon_.height() - badge.height());
52  }
53
54 private:
55  gfx::ImageSkia icon_;
56
57  DISALLOW_COPY_AND_ASSIGN(BadgedIconSource);
58};
59
60}  // namespace
61
62namespace app_list {
63
64WebstoreResult::WebstoreResult(Profile* profile,
65                               const std::string& app_id,
66                               const std::string& localized_name,
67                               const GURL& icon_url,
68                               bool is_paid,
69                               extensions::Manifest::Type item_type,
70                               AppListControllerDelegate* controller)
71    : profile_(profile),
72      app_id_(app_id),
73      localized_name_(localized_name),
74      icon_url_(icon_url),
75      is_paid_(is_paid),
76      item_type_(item_type),
77      controller_(controller),
78      install_tracker_(NULL),
79      extension_registry_(NULL),
80      weak_factory_(this) {
81  set_id(extensions::Extension::GetBaseURLFromExtensionId(app_id_).spec());
82  set_relevance(0.0);  // What is the right value to use?
83
84  set_title(base::UTF8ToUTF16(localized_name_));
85  SetDefaultDetails();
86
87  InitAndStartObserving();
88  UpdateActions();
89
90  int icon_dimension = GetPreferredIconDimension();
91  icon_ = gfx::ImageSkia(
92      new UrlIconSource(
93          base::Bind(&WebstoreResult::OnIconLoaded, weak_factory_.GetWeakPtr()),
94          profile_->GetRequestContext(),
95          icon_url_,
96          icon_dimension,
97          IDR_WEBSTORE_ICON_32),
98      gfx::Size(icon_dimension, icon_dimension));
99  SetIcon(icon_);
100}
101
102WebstoreResult::~WebstoreResult() {
103  StopObservingInstall();
104  StopObservingRegistry();
105}
106
107void WebstoreResult::Open(int event_flags) {
108  const GURL store_url = net::AppendQueryParameter(
109      GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + app_id_),
110      extension_urls::kWebstoreSourceField,
111      extension_urls::kLaunchSourceAppListSearch);
112
113  chrome::NavigateParams params(profile_,
114                                store_url,
115                                ui::PAGE_TRANSITION_LINK);
116  params.disposition = ui::DispositionFromEventFlags(event_flags);
117  chrome::Navigate(&params);
118}
119
120void WebstoreResult::InvokeAction(int action_index, int event_flags) {
121  if (is_paid_) {
122    // Paid apps cannot be installed directly from the launcher. Instead, open
123    // the webstore page for the app.
124    Open(event_flags);
125    return;
126  }
127
128  StartInstall(action_index == kLaunchEphemeralAppAction);
129}
130
131scoped_ptr<ChromeSearchResult> WebstoreResult::Duplicate() {
132  return scoped_ptr<ChromeSearchResult>(new WebstoreResult(profile_,
133                                                           app_id_,
134                                                           localized_name_,
135                                                           icon_url_,
136                                                           is_paid_,
137                                                           item_type_,
138                                                           controller_)).Pass();
139}
140
141void WebstoreResult::InitAndStartObserving() {
142  DCHECK(!install_tracker_ && !extension_registry_);
143
144  install_tracker_ =
145      extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
146  extension_registry_ = extensions::ExtensionRegistry::Get(profile_);
147
148  const extensions::ActiveInstallData* install_data =
149      install_tracker_->GetActiveInstall(app_id_);
150  if (install_data) {
151    SetPercentDownloaded(install_data->percent_downloaded);
152    SetIsInstalling(true);
153  }
154
155  install_tracker_->AddObserver(this);
156  extension_registry_->AddObserver(this);
157}
158
159void WebstoreResult::UpdateActions() {
160  Actions actions;
161
162  const bool is_otr = profile_->IsOffTheRecord();
163  const bool is_installed =
164      extensions::util::IsExtensionInstalledPermanently(app_id_, profile_);
165
166  if (!is_otr && !is_installed && !is_installing()) {
167    if (EphemeralAppLauncher::IsFeatureEnabled()) {
168      actions.push_back(Action(
169          l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_INSTALL),
170          l10n_util::GetStringUTF16(
171              IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE)));
172      if ((item_type_ == extensions::Manifest::TYPE_PLATFORM_APP ||
173           item_type_ == extensions::Manifest::TYPE_HOSTED_APP) &&
174          !is_paid_) {
175        actions.push_back(Action(
176            l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_LAUNCH),
177            l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_LAUNCH_APP_TOOLTIP)));
178      }
179    } else {
180      actions.push_back(Action(
181          l10n_util::GetStringUTF16(IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE),
182          base::string16()));
183    }
184  }
185
186  SetActions(actions);
187}
188
189void WebstoreResult::SetDefaultDetails() {
190  const base::string16 details =
191      l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE);
192  Tags details_tags;
193  details_tags.push_back(Tag(SearchResult::Tag::DIM, 0, details.length()));
194
195  set_details(details);
196  set_details_tags(details_tags);
197}
198
199void WebstoreResult::OnIconLoaded() {
200  // Remove the existing image reps since the icon data is loaded and they
201  // need to be re-created.
202  const std::vector<gfx::ImageSkiaRep>& image_reps = icon_.image_reps();
203  for (size_t i = 0; i < image_reps.size(); ++i)
204    icon_.RemoveRepresentation(image_reps[i].scale());
205  int icon_dimension = GetPreferredIconDimension();
206  gfx::Size icon_size(icon_dimension, icon_dimension);
207  icon_ = gfx::ImageSkia(new BadgedIconSource(icon_, icon_size), icon_size);
208
209  SetIcon(icon_);
210}
211
212void WebstoreResult::StartInstall(bool launch_ephemeral_app) {
213  SetPercentDownloaded(0);
214  SetIsInstalling(true);
215
216  if (launch_ephemeral_app) {
217    scoped_refptr<EphemeralAppLauncher> installer =
218        EphemeralAppLauncher::CreateForLauncher(
219            app_id_,
220            profile_,
221            controller_->GetAppListWindow(),
222            base::Bind(&WebstoreResult::LaunchCallback,
223                       weak_factory_.GetWeakPtr()));
224    installer->Start();
225    return;
226  }
227
228  scoped_refptr<WebstoreInstaller> installer =
229      new WebstoreInstaller(
230          app_id_,
231          profile_,
232          controller_->GetAppListWindow(),
233          base::Bind(&WebstoreResult::InstallCallback,
234                     weak_factory_.GetWeakPtr()));
235  installer->BeginInstall();
236}
237
238void WebstoreResult::InstallCallback(
239    bool success,
240    const std::string& error,
241    extensions::webstore_install::Result result) {
242  if (!success) {
243    LOG(ERROR) << "Failed to install app, error=" << error;
244    SetIsInstalling(false);
245    return;
246  }
247
248  // Success handling is continued in OnExtensionInstalled.
249  SetPercentDownloaded(100);
250}
251
252void WebstoreResult::LaunchCallback(extensions::webstore_install::Result result,
253                                    const std::string& error) {
254  if (result != extensions::webstore_install::SUCCESS)
255    LOG(ERROR) << "Failed to launch app, error=" << error;
256
257  SetIsInstalling(false);
258}
259
260void WebstoreResult::StopObservingInstall() {
261  if (install_tracker_)
262    install_tracker_->RemoveObserver(this);
263  install_tracker_ = NULL;
264}
265
266void WebstoreResult::StopObservingRegistry() {
267  if (extension_registry_)
268    extension_registry_->RemoveObserver(this);
269  extension_registry_ = NULL;
270}
271
272void WebstoreResult::OnDownloadProgress(const std::string& extension_id,
273                                        int percent_downloaded) {
274  if (extension_id != app_id_ || percent_downloaded < 0)
275    return;
276
277  SetPercentDownloaded(percent_downloaded);
278}
279
280void WebstoreResult::OnExtensionInstalled(
281    content::BrowserContext* browser_context,
282    const extensions::Extension* extension,
283    bool is_update) {
284  if (extension->id() != app_id_)
285    return;
286
287  SetIsInstalling(false);
288  UpdateActions();
289
290  if (extensions::util::IsExtensionInstalledPermanently(extension->id(),
291                                                        profile_)) {
292    NotifyItemInstalled();
293  }
294}
295
296void WebstoreResult::OnShutdown() {
297  StopObservingInstall();
298}
299
300void WebstoreResult::OnShutdown(extensions::ExtensionRegistry* registry) {
301  StopObservingRegistry();
302}
303
304ChromeSearchResultType WebstoreResult::GetType() {
305  return SEARCH_WEBSTORE_SEARCH_RESULT;
306}
307
308}  // namespace app_list
309