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